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

84 lines
4.6 KiB
Markdown

It's actually not hard to figure out which instructions go through which
pipes; just back up until you find an instruction that can't pair or can
only go through the U-pipe, and work forward from there, given the
knowledge that that instruction executes in the U-pipe. The easiest
thing to look for is branches. All branch target instructions execute in
the U-pipe, as do all instructions after conditional branches that fall
through. Instructions with prefix bytes are generally good U-pipe
markers, although they're expensive instructions that should be avoided
whenever possible, and have at least one aberration with regard to pipe
usage, as discussed below. Shifts, rotates, **ADC, SBB**, and all other
instructions not listed in Table 20.1 in the last chapter are likewise
U-pipe markers.
### Pentium Optimization in Action {#Heading7}
Now, let's take a look at one of the simplest, tightest pieces of code
imaginable, and see what our new Pentium perspective reveals. Listing
21.1 shows a loop implementing the TCP/IP checksum, a 16-bit checksum
that wraps carries around to the low bit so that the result is
endian-independent. This makes it easy to perform checksums on blocks of
data regardless of the endian characteristics of the machines on which
those blocks are generated and received. (Thanks to fellow performance
enthusiast Terje Mathisen for suggesting this checksum as fertile ground
for Pentium optimization, in the ibm.pc/fast.code forum on Bix.) The
loop in Listing 21.1 consists of exactly five instructions; it's hard to
imagine that there's a lot of performance to be wrung from this snippet,
right?
**LISTING 21.1 L21-1.ASM**
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; starting at ESI, of length ECX words.
; Returns checksum in AX.
; ECX and ESI destroyed.
; All cycle counts assume 32-bit protected mode.
; Assumes buffer length > 0.
; Note that timing indicates that the pipe sequence and
; cycle counts shown (based on documented execution rules)
; differ from the actual execution sequence and cycle counts;
; this loop has been measured to execute in 5 cycles; apparently,
; the 1st half of ADD somehow pairs with the prefix byte, or the
; refix byte gets executed ahead of time.
sub ax,ax ;initialize the checksum
ckloop:
add ax,[esi] ;cycle 1 U-pipe prefix byte
;cycle 1 V-pipe idle (no pairing w/prefix)
;cycle 2 U-pipe 1st half of ADD
;cycle 2 V-pipe idle (register contention)
;cycle 3 U-pipe 2nd half of ADD
;cycle 3 V-pipe idle (register contention)
adc ax,0 ;cycle 4 U-pipe prefix byte
;cycle 4 V-pipe idle (no pairing w/prefix)
;cycle 5 U-pipe ADC AX,0
add esi,2 ;cycle 5 V-pipe
dec ecx ;cycle 6 U-pipe
jnz ckloop ;cycle 6 V-pipe
Wrong, wrong, wrong! As detailed in Listing 21.1, this loop should take
6 cycles per checksummed word in 32-bit protected mode, a ridiculously
high number for the Pentium. (You'll see why I say "should take," not
"takes," shortly.) We should lose 2 cycles in each pipe to the two size
prefixes (because the **ADD**s are 16-bit operations in a 32-bit
segment), and another 2 cycles because of register contention that
arises when **ADC AX,0** has to wait for the result of **ADD AX,[ESI]**.
Then, too, even though **DEC** and **JNZ** can pair and the branch
prediction for **JNZ** is presumably correct virtually all the time,
they do take a full cycle, and maybe we can do something about that as
well.
The first thing to do is to time the code in Listing 21.1 to verify our
analysis. When I unleashed the Zen timer on Listing 21.1, I found, to my
surprise, that the code actually takes only five cycles per checksum
word processed, not six. A little more experimentation revealed that
adding a size prefix to the two-cycle **ADD EAX,[ESI]** instruction
doesn't cost anything, certainly not the one full cycle in each pipe
that a prefix is supposed to take. More experimentation showed that
prefix bytes do cost the documented extra cycle when used with one-cycle
instructions such as **MOV**. At this point, my preliminary conclusion
is that prefixes can pair with the first cycle of at least some
multiple-cycle instructions. Determining exactly why this happens will
take further research on my part, but the most important conclusion is
that you *must* measure your code!