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

114 lines
No EOL
6.2 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Chapter 13\
Aiming the 486 {#Heading1}
---------------
### Pipelines and Other Hazards of the High End {#Heading2}
It's a sad but true fact that 84 percent of American schoolchildren are
ignorant of 92 percent of American history. Not my daughter, though. We
recently visited historical Revolutionary-War-vintage Fort Ticonderoga,
and she's now 97 percent aware of a key element of our national
heritage: that the basic uniform for soldiers in those days was what
appears to be underwear, plus a hat so that no one could complain that
they were undermining family values. Ha! Just kidding! Actually, what
she learned was that in those days, it was pure coincidence if a
cannonball actually hit anything it was aimed at, which isn't surprising
considering the lack of rifling, precision parts, and ballistics. The
guides at the fort shot off three cannons; the closest they came to the
target was about 50 feet, and that was only because the wind helped. I
think the idea in early wars was just to put so much lead in the air
that some of it was bound to hit *something;* preferably, but not
necessarily, the enemy.
Nowadays, of course, we have automatic weapons that allow a teenager to
singlehandedly defeat the entire U.S. Army, not to mention so-called
"smart" bombs, which are smart in the sense that they can seek out and
empty a taxpayer's wallet without being detected by radar. There's an
obvious lesson here about progress, which I leave you to deduce for
yourselves.
Here's the same lesson, in another form. Ten years ago, we had a slow
processor, the 8088, for which it was devilishly hard to optimize, and
for which there was no good optimization documentation available. Now we
have a processor, the 486, that's 50 to 100 times faster than the
8088—and for which there is no good optimization documentation
available. Sure, Intel provides a few tidbits on optimization in the
back of the *i486 Microprocessor Programmer's Reference Manual,* but, as
I discussed in Chapter 12, that information is both incomplete and not
entirely correct. Besides, most assembly language programmers don't
bother to read Intel's manuals (which are extremely informative and well
done, but only slightly more fun to read than the phone book), and go
right on programming the 486 using outdated 8088 optimization
techniques, blissfully unaware of a new and heavily mutated generation
of cycle-eaters that interact with their code in ways undreamt of even
on the 386.
For example, consider how Terje Mathisen doubled the speed of his
word-counting program on a 486 simply by shuffling a couple of
instructions.
#### 486 Pipeline Optimization {#Heading3}
I've mentioned Terje Mathisen in my writings before. Terje is an
assembly language programmer extraordinaire, and author of the
incredibly fast public-domain word-counting program WC (which comes
complete with source code; well worth a look, if you want to see what
*really* fast code looks like). Terje's a regular participant in the
ibm.pc/fast.code topic on Bix. In a thread titled "486 Pipeline
Optimization, or TANSTATFC (There Ain't No Such Thing As The Fastest
Code)," he detailed the following optimization to WC, perhaps the best
example of 486 pipeline optimization I've yet seen.
Terje's inner loop originally looked something like the code in Listing
13.1. (I've taken a few liberties for illustrative purposes.) Of course,
Terje unrolls this loop a few times (128 times, to be exact). By the
way, in Listing 13.1 you'll notice that Terje counts not only words but
also lines, at a rate of three instructions for every two characters!
**LISTING 13.1 L13-1.ASM**
mov di,[bp+OFFS] ;get the next pair of characters
mov bl,[di] ;get the state value for the pair
add dx,[bx+8000h] ;increment word and line count
; appropriately for the pair
Listing 13.1 looks as tight as it could be, with just two one-cycle
instructions, one two-cycle instruction, and no branches. It *is* tight,
but those three instructions actually take a minimum of 8 cycles to
execute, as shown in Figure 13.1. The problem is that DI is loaded just
before being used to address memory, and that costs 2 cycles because it
interrupts the 486's internal instruction pipeline. Likewise, BX is
loaded just before being used to address memory, costing another two
cycles. Thus, this loop takes twice as long as cycle counts would seem
to indicate, simply because two registers are loaded immediately before
being used, disrupting the 486's pipeline.
Listing 13.2 shows Terje's immediate response to these pipelining
problems; he simply swapped the instructions that load DI and BL. This
one change cut execution time per character pair from eight cycles to
five cycles! The load of BL is now separated by one instruction from the
use of BX to address memory, so the pipeline penalty is reduced from two
cycles to one cycle. The load of DI is also separated by one instruction
from the use of DI to address memory (remember, the loop is unrolled, so
the last instruction is followed by the first instruction), but because
the intervening instruction takes two cycles, there's no penalty at all.
![](images/13-01.jpg)\
**Figure 13.1**  *Cycle-eaters in the original WC.*
------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *Remember, pipeline penalties diminish with increasing number of cycles, not instructions, between the pipeline disrupter and the potentially affected instruction.*
------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------
**LISTING 13.2 L13-2.ASM**
mov bl,[di] ;get the state value for the pair
mov di,[bp+OFFS] ;get the next pair of characters
add dx,[bx+8000h] ;increment word and line count
; appropriately for the pair
At this point, Terje had nearly doubled the performance of this code
simply by moving one instruction. (Note that swapping the instructions
also made it necessary to preload DI at the start of the loop; Listing
13.2 is not exactly equivalent to Listing 13.1.) I'll let Terje describe
his next optimization in his own words: