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

107 lines
No EOL
5.1 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"When I looked closely as this, I realized that the two cycles for the
final **ADD** is just the sum of 1 cycle to load the data from memory,
and 1 cycle to add it to DX, so the code could just as well have been
written as shown in Listing 13.3. The final breakthrough came when I
realized that by initializing AX to zero outside the loop, I could
rearrange it as shown in Listing 13.4 and do the final **ADD DX,AX**
after the loop. This way there are two single-cycle instructions between
the first and the fourth line, avoiding all pipeline stalls, for a total
throughput of two cycles/char."
**LISTING 13.3 L13-3.ASM**
mov bl,[di] ;get the state value for the pair
mov di,[bp+OFFS] ;get the next pair of characters
mov ax,[bx+8000h] ;increment word and line count
add dx,ax ; appropriately for the pair
**LISTING 13.4 L13-4.ASM**
mov bl,[di] ;get the state value for the pair
mov di,[bp+OFFS] ;get the next pair of characters
add dx,ax ;increment word and line count
; appropriately for the pair
mov ax,[bx+8000h] ;get increments for next time
I'd like to point out two fairly remarkable things. First, the single
cycle that Terje saved in Listing 13.4 sped up his entire word-counting
engine by 25 percent or more; Listing 13.4 is fully twice as fast as
Listing 13.1—all the result of nothing more than shifting an instruction
and splitting another into two operations. Second, Terje's word-counting
engine can process more than 16 million characters *per second* on a
486/33.
Clever 486 optimization can pay off big. QED.
### BSWAP: More Useful Than You Might Think {#Heading4}
There are only 3 non-system instructions unique to the 486. None is
earthshaking, but they have their uses. Consider **BSWAP. BSWAP** does
just what its name implies, swapping the bytes (not bits) of a 32-bit
register from one end of the register to the other, as shown in Figure
13.2. (**BSWAP** can only work with 32-bit registers; memory locations
and 16-bit registers are not valid operands.) The obvious use of
**BSWAP** is to convert data from Intel format (least significant byte
first in memory, also called *little endian*) to Motorola format (most
significant byte first in memory, or *big endian*), like so:
lodsd
bswap
stosd
**BSWAP** can also be useful for reversing the order of pixel bits from
a bitmap so that they can be rotated 32 bits at a time with an
instruction such as **ROR EAX,1**. Intel's byte ordering for multiword
values (least-significant byte first) loads pixels in the wrong order,
so far as word rotation is concerned, but **BSWAP** can take care of
that.
![](images/13-02.jpg)\
**Figure 13.2**  *BSWAP in operation.*
As it turns out, though, **BSWAP** is also useful in an unexpected way,
having to do with making efficient use of the upper half of 32-bit
registers. As any assembly language programmer knows, the x86 register
set is too small; or, to phrase that another way, it sure would be nice
if the register set were bigger. As any 386/486 assembly language
programmer knows, there are many cases in which 16 bits is plenty. For
example, a 16-bit scan-line counter generally does the trick nicely in a
video driver, because there are *very* few video devices with more than
65,535 addressable scan lines. Combining these two observations yields
the obvious conclusion that it would be great if there were some way to
use the upper and lower 16 bits of selected 386 registers as separate
16-bit registers, effectively increasing the available register space.
Unfortunately, the x86 instruction set doesn't provide any way to work
directly with only the upper half of a 32-bit register. The next best
solution is to rotate the register to give you access in the lower 16
bits to the half you need at any particular time, with code along the
lines of that in Listing 13.5. Having to rotate the 16-bit fields into
position certainly isn't as good as having direct access to the upper
half, but surely it's better than having to get the values out of
memory, isn't it?
**LISTING 13.5 L13-5.ASM**
mov cx,[initialskip]
shl ecx,16 ;put skip value in upper half of ECX
mov cx,100 ;put loop count in CX
looptop:
:
ror ecx,16 ;make skip value word accessible in CX
add bx,cx ;skip BX ahead
inc cx ;set next skip value
ror ecx,16 ;put loop count in CX
dec cx ;count down loop
jnz looptop
Not necessarily. Shifts and rotates are among the worst performing
instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes
2 cycles to rotate the skip value into CX in Listing 13.5, and 2 more
cycles to rotate it back to the upper half of ECX. I'd say four cycles
is a pretty steep price to pay, especially considering that a **MOV** to
or from memory takes only one cycle. Basically, using **ROR** to access
a 16-bit value in the upper half of a 16-bit register is a pretty
marginal technique, unless for some reason you can't access memory at
all (for example, if you're using BP as a working register, temporarily
making the stack frame inaccessible).