140 lines
8 KiB
HTML
140 lines
8 KiB
HTML
<!DOCTYPE html>
|
|
|
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<meta name="vsisbn" content="1576101746" />
|
|
<meta name="vstitle" content="Michael Abrash's Graphics Programming Black Book, Special Edition" />
|
|
<meta name="vsauthor" content="Michael Abrash" />
|
|
<meta name="vspublisher" content="The Coriolis Group" />
|
|
<meta name="vspubdate" content="07/01/97" />
|
|
<meta name="vscategory" content="Web and Software Development: Game Development,Web and Software Development: Graphics and Multimedia Development" />
|
|
|
|
<title>Michael Abrash's Graphics Programming Black Book Special Edition: Hints My Readers Gave Me</title>
|
|
<meta name="chapter" content="09" />
|
|
<meta name="pages" content="185-188" />
|
|
</head>
|
|
|
|
<body>
|
|
<center>
|
|
<table border="1">
|
|
<tr>
|
|
<td>
|
|
<a href="09-06.html">Previous</a>
|
|
</td>
|
|
|
|
<td>
|
|
<a href="index.html">Table of Contents</a>
|
|
</td>
|
|
|
|
<td>
|
|
<a href="10-01.html">Next</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</center>
|
|
|
|
<h4 id="Heading10">Hard-Core Cycle Counting</h4>
|
|
|
|
<p>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 <b>RCR</b> and <b>RCL</b> 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 <i>multibit</i> rotate-through-carries, which I’ve timed at 8 cycles apiece; for example, <b>RCR AX,CL</b> takes 8 cycles on <i>my</i> 386, as does <b>RCL DX,2</b>. Contrast that with <b>ROR</b> and <b>ROL</b>, which can rotate the contents of a register any number of bits in just 3 cycles.</p>
|
|
|
|
<p>However, rotating by one bit through the Carry flag does <i>not</i> take 9 cycles, contrary to Intel’s <i>80386 Programmer’s Reference Manual</i>, or even 8 cycles. In fact, <b>RCR</b> <i>reg</i>,1 and <b>RCL</b> <i>reg</i>,1 take 3 cycles, just like <b>ROR, ROL, SHR,</b> and <b>SHL</b>. 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!)</p>
|
|
|
|
<p><a id="Fig4"><img src="images/09-04.jpg" /><br />
|
|
<b>Figure 9.4</b></a> <i>Performing rotate instructions using the Carry flag.</i></p>
|
|
|
|
<p>Interestingly, according to Intel’s <i>i486 Microprocessor Programmer’s Reference Manual</i>, the 486 can <b>RCR</b> or <b>RCL</b> a register by one bit in 3 cycles, but takes between 8 and 30 cycles to perform a multibit register <b>RCR</b> or <b>RCL</b>!</p>
|
|
|
|
<p>No great lesson here, just a caution to be leery of multibit <b>RCR</b> and <b>RCL</b> when performance matters—and to take cycle-time documentation with a grain of salt.</p>
|
|
|
|
<h4 id="Heading11">Hardwired Far Jumps</h4>
|
|
|
|
<p>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 <b>JMP FAR PTR 1000:5</b>, 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:</p>
|
|
<pre>
|
|
Ptr dd ?
|
|
:
|
|
mov word ptr [Ptr],5
|
|
mov word ptr [Ptr+2],1000h
|
|
jmp [Ptr]
|
|
</pre>
|
|
|
|
<p>That will work, but at a price in performance. On an 8088, <b>JMP DWORD PTR [<i>mem</i>]</b> (an indirect far jump) takes at least 37 cycles; <b>JMP DWORD PTR <i>label</i></b> (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.</p>
|
|
|
|
<p>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.</p>
|
|
|
|
<p>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 <b>FarLabel</b> 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.</p>
|
|
|
|
<p><b>LISTING 9.7 L9-7.ASM</b></p>
|
|
<pre>
|
|
; 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
|
|
</pre>
|
|
|
|
<p>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:</p>
|
|
|
|
<p>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.</p>
|
|
|
|
<h4 id="Heading12">Setting 32-Bit Registers: Time versus Space</h4>
|
|
|
|
<p>To finish up this chapter, consider these two items. First, in 32-bit protected mode,</p>
|
|
<pre>
|
|
sub eax,eax
|
|
inc eax
|
|
</pre>
|
|
|
|
<p>takes 4 cycles to execute, but is only 3 bytes long, while</p>
|
|
<pre>
|
|
mov eax,1
|
|
</pre>
|
|
|
|
<p>takes only 2 cycles to execute, but is 5 bytes long (because native mode constants are dwords and the <b>MOV</b> instruction doesn’t sign-extend). Both code fragments are ways to set <b>EAX</b> to 1 (although the first affects the flags and the second doesn’t); this is a classic trade-off of speed for space. Second,</p>
|
|
<pre>
|
|
or ebx,-1
|
|
</pre>
|
|
|
|
<p>takes 2 cycles to execute and is 3 bytes long, while</p>
|
|
<pre>
|
|
move bx,-1
|
|
</pre>
|
|
|
|
<p>takes 2 cycles to execute and is 5 bytes long. Both instructions set <b>EBX</b> to -1; this is a classic trade-off of—gee, it’s not a trade-off at all, is it? <b>OR</b> is a better way to set a 32-bit register to all 1-bits, just as <b>SUB</b> or <b>XOR</b> 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.</p>
|
|
|
|
<p>Be warned, though, that I’ve found <b>OR, AND, ADD</b>, and the like to be a cycle slower than <b>MOV</b> 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.</p>
|
|
|
|
<center>
|
|
<table border="1">
|
|
<tr>
|
|
<td>
|
|
<a href="09-06.html">Previous</a>
|
|
</td>
|
|
|
|
<td>
|
|
<a href="index.html">Table of Contents</a>
|
|
</td>
|
|
|
|
<td>
|
|
<a href="10-01.html">Next</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</center>
|
|
<hr width="90%" size="1" noshade="noshade" />
|
|
|
|
<div align="center">
|
|
Graphics Programming Black Book © 2001 Michael Abrash
|
|
</div>
|
|
</body>
|
|
</html>
|