131 lines
No EOL
6.5 KiB
Markdown
131 lines
No EOL
6.5 KiB
Markdown
---
|
|
title: Michael Abrash's Graphics Programming Black Book, Special Edition
|
|
author: Michael Abrash
|
|
date: '1997-07-01'
|
|
isbn: '1576101746'
|
|
publisher: The Coriolis Group
|
|
category: 'Web and Software Development: Game Development,Web and Software Development:
|
|
Graphics and Multimedia Development'
|
|
chapter: 08
|
|
pages: 149-153
|
|
---
|
|
|
|
## 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.
|
|
|
|
> 
|
|
> 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.
|
|
|
|
> 
|
|
> 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? |