Previous | Table of Contents | Next

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

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 ADDs 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 | Table of Contents | Next

Graphics Programming Black Book © 2001 Michael Abrash