abrash-black-book/08-01.md
2013-12-30 20:26:41 +11:00

113 lines
No EOL
7.6 KiB
Markdown

Chapter 8\
Speeding Up C with Assembly Language {#Heading1}
-------------------------------------
### Jumping Languages When You Know It'll Help {#Heading2}
When I was a senior in high school, a pop song called "Seasons in the
Sun," sung by one Terry Jacks, soared up the pop charts and spent, as
best I can recall, two straight weeks atop *Kasey Kasem's American Top
40.* "Seasons in the Sun" wasn't a particularly good song, primarily
because the lyrics were silly. I've never understood why the song was a
hit, but, as so often happens with undistinguished but popular music by
forgotten one- or two-shot groups ("Don't Pull Your Love Out on Me
Baby," "Billy Don't Be a Hero," *et al.*), I heard it everywhere for a
month or so, then gave it not another thought for 15 years.
Recently, though, I came across a review of a Rhino Records collection
of obscure 1970s pop hits. Knowing that Jeff Duntemann is an aficionado
of such esoterica (who do *you* know who owns an album by The Peppermint
Trolley Company?), I sent the review to him. He was amused by it and, as
we kicked the names of old songs around, "Seasons in the Sun" came up. I
expressed my wonderment that a song that really wasn't very good was
such a big hit.
"Well," said Jeff, "I think it suffered in the translation from the
French."
Ah-ha! Mystery solved. Apparently everyone but me knew that it was
translated from French, and that novelty undoubtedly made the song a big
hit. The translation was also surely responsible for the sappy lyrics;
dollars to donuts that the original French lyrics were stronger.
Which brings us without missing a beat to this chapter's theme, speeding
up C with assembly language. When you seek to speed up a C program by
converting selected parts of it (generally no more than a few functions)
to assembly language, make sure you end up with high-performance
assembly language code, not fine-tuned C code. Compilers like Microsoft
C/C++ and Watcom C are by now pretty good at fine-tuning C code, and
you're not likely to do much better by taking the compiler's assembly
language output and tweaking it.
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *To make the process of translating C code to assembly language worth the trouble, you must ignore what the compiler does and design your assembly language code from a pure assembly language perspective. With a merely adequate translation, you risk laboring mightily for little or no reward.*
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Apropos of which, when was the last time you heard of Terry Jacks?
#### Billy, Don't Be a Compiler {#Heading3}
The key to optimizing C programs with assembly language is, as always,
writing good assembly language code, but with an added twist. Rule 1
when converting C code to assembly is this: *Don't think like a
compiler.* That's more easily said than done, especially when the C code
you're converting is readily available as a model and the assembly code
that the compiler generates is available as well. Nevertheless, the
principle of not thinking like a compiler is essential, and is, in one
form or another, the basis for all that I'll discuss below.
Before I discuss Rule 1 further, let me mention rule number 0: *Only
optimize where it matters.* The bulk of execution time in any program is
spent in a very small portion of the code, and most code beyond that
small portion doesn't have any perceptible impact on performance. Unless
you're supremely concerned with code size (an area in which
assembly-only programs can excel), I'd suggest that you write most of
your code in C and reserve assembly for the truly critical sections of
your code; that's the formula that I find gives the most bang for the
buck.
This is not to say that complete programs shouldn't be *designed* with
optimized assembly language in mind. As you'll see shortly, orienting
your data structures towards assembly language can be a salubrious
endeavor indeed, even if most of your code is in C. When it comes to
actually optimizing code and/or converting it to assembly, though, do it
only where it matters. Get a profiler—and use it!
Also make it a point to concentrate on refining your program design and
algorithmic approach at the conceptual and/or C levels before doing any
assembly language optimization.
------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *Assembly language optimization is the final and far from the only step in the optimization chain, and as such should be performed last; converting to assembly too soon can lock in your code before the design is optimal. At the very least, conversion to assembly tends to make future changes and debugging more difficult, slowing you down and limiting your options.*
------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
### Don't Call Your Functions on Me, Baby {#Heading4}
In order to think differently from a compiler, you must understand both
what compilers and C programmers tend to do and how that differs from
what assembly language does well. In this pursuit, it can be useful to
examine the code your compiler generates, either by viewing the code in
a debugger or by having the compiler generate an assembly language
output file. (The latter is done with /Fa or /Fc in Microsoft C/C++ and
-S in Borland C++.)
C programmers tend to modularize their code with lots of function calls.
That's good for readable, reliable, reusable code, and it allows the
compiler to optimize better because it can deal with fewer variables and
statements in each optimization arena—but it's not so good when viewed
from the assembly language level. Calls and returns are slow, especially
in the large code model, and the pushes required to put parameters on
the stack are expensive as well.
What this means is that when you want to speed up a portion of a C
program, you should identify the entire critical portion and move *all*
of that critical portion into an assembly language function. You don't
want to move a part of the inner loop into assembly language and then
call it from C every time through the loop; the function call and return
overhead would be unacceptable. Carve out the critical code *en masse*
and move it into assembly, and try to avoid calls and returns even in
your assembly code. True, in assembly you can pass parameters in
registers, but the calls and returns themselves are still slow; if the
extra cycles they take don't affect performance, then the code they're
in probably isn't critical, and perhaps you've chosen to convert too
much code to assembly, eh?