96 lines
5.2 KiB
Markdown
96 lines
5.2 KiB
Markdown
------------------------ --------------------------------- --------------------
|
||
[Previous](21-02.html) [Table of Contents](index.html) [Next](21-04.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
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!
|
||
|
||
------------------------ --------------------------------- --------------------
|
||
[Previous](21-02.html) [Table of Contents](index.html) [Next](21-04.html)
|
||
------------------------ --------------------------------- --------------------
|
||
|
||
* * * * *
|
||
|
||
Graphics Programming Black Book © 2001 Michael Abrash
|