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

101 lines
No EOL
5.6 KiB
Markdown

On the 386, **ROR** was the only way to split a 32-bit register into two
16-bit registers. On the 486, however, **BSWAP** can not only do the
job, but can do it better, because **BSWAP** executes in just one cycle.
**BSWAP** has the added benefit of not affecting any flags, unlike
**ROR**. With **BSWAP**-based code like that in Listing 13.6, the upper
16 bits of a register can be accessed with only 2 cycles of overhead and
without altering any flags, making the technique of packing two 16-bit
registers into one 32-bit register much more useful.
**LISTING 13.6 L13-6.ASM**
mov cx,[initialskip]
bswap ecx ;put skip value in upper half of ECX
mov cx,100 ;put loop count in CX
looptop:
:
bswap ecx ;make skip value word accessible in CX
add bx,cx ;skip BX ahead
inc cx ;set next skip value
bswap ecx ;put loop count in CX
dec cx ;count down loop
jnz looptop
### Pushing and Popping Memory {#Heading5}
Pushing or popping a memory location, as in **PUSH WORD PTR [BX]** or
**POP [MemVar]**, is a compact, easy way to get a value onto or off of
the stack, especially when pushing parameters for calling a C-compatible
function. However, on a 486, these are unattractive instructions from a
performance perspective. Pushing a memory location takes four cycles; by
contrast, loading a memory location into a register takes only one
cycle, and pushing a register takes just 1 more cycle, for a total of
two cycles. Therefore,
mov ax,[bx]
push ax
is twice as fast as
push word ptr [bx]
and the only cost is that the previous contents of AX are destroyed.
Likewise, popping a memory location takes six cycles, but popping a
register and writing it to memory takes only two cycles combined. The
*i486 Microprocessor Programmer's Reference Manual* lists a 4-cycle
execution time for popping a register, but pay that no mind; popping a
register takes only 1 cycle.
Why is it that such a convenient operation as pushing or popping memory
is so slow? The rule on the 486 is that simple operations, which can be
executed in a single cycle by the 486's RISC core, are fast; whereas
complex operations, which must be carried out in microcode just as they
were on the 386, are almost all relatively slow. Slow, complex
operations include all the string instructions except **REP MOVS,** as
well as **XLAT, LOOP,** and, of course, **PUSH *mem*** and **POP
*mem.***
------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *Whenever possible, try to use the 486's 1-cycle instructions, including **MOV, ADD, SUB, CMP, ADC, SBB, XOR, AND, OR, TEST, LEA**, and **PUSH reg** and **POP reg**. These instructions have an added benefit in that it's often possible to rearrange them for maximum pipeline efficiency, as is the case with Terje's optimization described earlier in this chapter.*
------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
### Optimal 1-Bit Shifts and Rotates {#Heading6}
On a 486, the n-bit forms of the shift and rotate instructions—as in
**ROR AX,2** and **SHL BX,9**—are 2-cycle instructions, but the 1-bit
forms—as in **ROR AX,1** and **SHL BX,1—**are *3-cycle* instructions. Go
figure.
Assemblers default to the 1-bit instruction for 1-bit shifts and
rotates. That's not unreasonable since the 1-bit form is a byte shorter
and is just as fast as the n-bit forms on a 386 and faster on a 286, and
the n-bit form doesn't even exist on an 8088. In a really critical loop,
however, it might be worth hand-assembling the n-bit form of a
single-bit shift or rotate in order to save that cycle. The easiest way
to do this is to assemble a 2-bit form of the desired instruction, as in
**SHL AX,2,** then look at the hex codes that the assembler generates
and use **DB** to insert them in your program code, with the value two
replaced with the value one. For example, you could determine that **SHL
AX,2** assembles to the bytes 0C1H 0E0H 002H, either by looking at the
disassembly in a debugger or by having the assembler generate a listing
file. You could then insert the n-bit version of **SHL AX,1** in your
code as follows:
mov ax,1
db 0c1h, 0e0h, 001h
mov dx,ax
At the end of this sequence, DX will contain 2, and the fast n-bit
version of **SHL AX,1** will have executed. If you use this approach,
I'd recommend using a macro, rather than sticking DBs in the middle of
your code.
Again, this technique is advantageous *only* on a 486. It also doesn't
apply to **RCL** and **RCR,** where you definitely want to use the 1-bit
versions whenever you can, because the n-bit versions are horrendously
slow. But if you're optimizing for the 486, these tidbits can save a few
critical cycles—and Lord knows that if you're optimizing for the
486—that is, if you need even more performance than you get from
unoptimized code on a 486—you almost certainly need all the speed you
can get.