84 lines
4.6 KiB
Markdown
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!
|