Initial conversion to markdown
This commit is contained in:
parent
a96a114403
commit
5e10794a24
362 changed files with 67353 additions and 0 deletions
151
09-07.md
Normal file
151
09-07.md
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
------------------------ --------------------------------- --------------------
|
||||
[Previous](09-06.html) [Table of Contents](index.html) [Next](10-01.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
#### Hard-Core Cycle Counting {#Heading10}
|
||||
|
||||
Next, we come to an item that cycle counters will love, especially since
|
||||
it involves apparently incorrect documentation on Intel’s part.
|
||||
According to Intel’s documents, all **RCR** and **RCL** instructions,
|
||||
which perform rotations through the Carry flag, as shown in Figure 9.4,
|
||||
take 9 cycles on the 386 when working with a register operand. My
|
||||
measurements indicate that the 9-cycle execution time almost holds true
|
||||
for *multibit* rotate-through-carries, which I’ve timed at 8 cycles
|
||||
apiece; for example, **RCR AX,CL** takes 8 cycles on *my* 386, as does
|
||||
**RCL DX,2**. Contrast that with **ROR** and **ROL**, which can rotate
|
||||
the contents of a register any number of bits in just 3 cycles.
|
||||
|
||||
However, rotating by one bit through the Carry flag does *not* take 9
|
||||
cycles, contrary to Intel’s *80386 Programmer’s Reference Manual*, or
|
||||
even 8 cycles. In fact, **RCR** *reg*,1 and **RCL** *reg*,1 take 3
|
||||
cycles, just like **ROR, ROL, SHR,** and **SHL**. At least, that’s how
|
||||
fast they run on my 386, and I very much doubt that you’ll find
|
||||
different execution times on other 386s. (Please let me know if you do,
|
||||
though!)
|
||||
|
||||
\
|
||||
**Figure 9.4** *Performing rotate instructions using the Carry flag.*
|
||||
|
||||
Interestingly, according to Intel’s *i486 Microprocessor Programmer’s
|
||||
Reference Manual*, the 486 can **RCR** or **RCL** a register by one bit
|
||||
in 3 cycles, but takes between 8 and 30 cycles to perform a multibit
|
||||
register **RCR** or **RCL**!
|
||||
|
||||
No great lesson here, just a caution to be leery of multibit **RCR** and
|
||||
**RCL** when performance matters—and to take cycle-time documentation
|
||||
with a grain of salt.
|
||||
|
||||
#### Hardwired Far Jumps {#Heading11}
|
||||
|
||||
Did you ever wonder how to code a far jump to an absolute address in
|
||||
assembly language? Probably not, but if you ever do, you’re going to be
|
||||
glad for this next item, because the obvious solution doesn’t work. You
|
||||
might think all it would take to jump to, say, 1000:5 would be **JMP FAR
|
||||
PTR 1000:5**, but you’d be wrong. That won’t even assemble. You might
|
||||
then think to construct in memory a far pointer containing 1000:5, as in
|
||||
the following:
|
||||
|
||||
Ptr dd ?
|
||||
:
|
||||
mov word ptr [Ptr],5
|
||||
mov word ptr [Ptr+2],1000h
|
||||
jmp [Ptr]
|
||||
|
||||
That will work, but at a price in performance. On an 8088, **JMP DWORD
|
||||
PTR [*mem*]** (an indirect far jump) takes at least 37 cycles; **JMP
|
||||
DWORD PTR *label*** (a direct far jump) takes only 15 cycles (plus,
|
||||
almost certainly, some cycles for instruction fetching). On a 386, an
|
||||
indirect far jump is documented to take at least 43 cycles in real mode
|
||||
(31 in protected mode); a direct far jump is documented to take at least
|
||||
12 cycles, about three times faster. In truth, the difference between
|
||||
those two is nowhere near that big; the fastest I’ve measured for a
|
||||
direct far jump is 21 cycles, and I’ve measured indirect far jumps as
|
||||
fast as 30 cycles, so direct is still faster, but not by so much. (Oh,
|
||||
those cycle-time documentation blues!) Also, a direct far jump is
|
||||
documented to take at least 27 cycles in protected mode; why the big
|
||||
difference in protected mode, I have no idea.
|
||||
|
||||
At any rate, to return to our original problem of jumping to 1000:5:
|
||||
Although an indirect far jump will work, a direct far jump is still
|
||||
preferable.
|
||||
|
||||
Listing 9.7 shows a short program that performs a direct far call to
|
||||
1000:5. (Don’t run it, unless you want to crash your system!) It does
|
||||
this by creating a dummy segment at 1000H, so that the label
|
||||
**FarLabel** can be created with the desired far attribute at the proper
|
||||
location. (Segments created with “AT” don’t cause the generation of any
|
||||
actual bytes or the allocation of any memory; they’re just templates.)
|
||||
It’s a little kludgey, but at least it does work. There may be a better
|
||||
solution; if you have one, pass it along.
|
||||
|
||||
**LISTING 9.7 L9-7.ASM**
|
||||
|
||||
; Program to perform a direct far jump to address 1000:5.
|
||||
; *** Do not run this program! It’s just an example of how ***
|
||||
; *** to build a direct far jump to an absolute address ***
|
||||
;
|
||||
; Tested with TASM 2 and MASM 5.
|
||||
|
||||
FarSeg segment at 01000h
|
||||
org 5
|
||||
FarLabel label far
|
||||
FarSeg ends
|
||||
|
||||
.model small
|
||||
.code
|
||||
start:
|
||||
jmp FarLabel
|
||||
end start
|
||||
|
||||
By the way, if you’re wondering how I figured this out, I merely applied
|
||||
my good friend Dan Illowsky’s long-standing rule for dealing with MASM:
|
||||
|
||||
If the obvious doesn’t work (and it usually doesn’t), just try
|
||||
everything you can think of, no matter how ridiculous, until you find
|
||||
something that does—a rule with plenty of history on its side.
|
||||
|
||||
#### Setting 32-Bit Registers: Time versus Space {#Heading12}
|
||||
|
||||
To finish up this chapter, consider these two items. First, in 32-bit
|
||||
protected mode,
|
||||
|
||||
sub eax,eax
|
||||
inc eax
|
||||
|
||||
takes 4 cycles to execute, but is only 3 bytes long, while
|
||||
|
||||
mov eax,1
|
||||
|
||||
takes only 2 cycles to execute, but is 5 bytes long (because native mode
|
||||
constants are dwords and the **MOV** instruction doesn’t sign-extend).
|
||||
Both code fragments are ways to set **EAX** to 1 (although the first
|
||||
affects the flags and the second doesn’t); this is a classic trade-off
|
||||
of speed for space. Second,
|
||||
|
||||
or ebx,-1
|
||||
|
||||
takes 2 cycles to execute and is 3 bytes long, while
|
||||
|
||||
move bx,-1
|
||||
|
||||
takes 2 cycles to execute and is 5 bytes long. Both instructions set
|
||||
**EBX** to -1; this is a classic trade-off of—gee, it’s not a trade-off
|
||||
at all, is it? **OR** is a better way to set a 32-bit register to all
|
||||
1-bits, just as **SUB** or **XOR** is a better way to set a register to
|
||||
all 0-bits. Who woulda thunk it? Just goes to show how the 32-bit
|
||||
displacements and constants of 386 native mode change the familiar
|
||||
landscape of 80x86 optimization.
|
||||
|
||||
Be warned, though, that I’ve found **OR, AND, ADD**, and the like to be
|
||||
a cycle slower than **MOV** when working with immediate operands on the
|
||||
386 under some circumstances, for reasons that thus far escape me. This
|
||||
just reinforces the first rule of optimization: Measure your code in
|
||||
action, and place not your trust in documented cycle times.
|
||||
|
||||
------------------------ --------------------------------- --------------------
|
||||
[Previous](09-06.html) [Table of Contents](index.html) [Next](10-01.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
* * * * *
|
||||
|
||||
Graphics Programming Black Book © 2001 Michael Abrash
|
||||
Loading…
Reference in a new issue