abrash-black-book/09-07.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&rsquo;s part. According to Intel&rsquo;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&rsquo;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&rsquo;s <i>80386 Programmer&rsquo;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&rsquo;s how fast they run on my 386, and I very much doubt that you&rsquo;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>&nbsp;&nbsp;<i>Performing rotate instructions using the Carry flag.</i></p>
<p>Interestingly, according to Intel&rsquo;s <i>i486 Microprocessor Programmer&rsquo;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&mdash;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&rsquo;re going to be glad for this next item, because the obvious solution doesn&rsquo;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&rsquo;d be wrong. That won&rsquo;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&rsquo;ve measured for a direct far jump is 21 cycles, and I&rsquo;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&rsquo;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 &ldquo;AT&rdquo; don&rsquo;t cause the generation of any actual bytes or the allocation of any memory; they&rsquo;re just templates.) It&rsquo;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&rsquo;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&rsquo;re wondering how I figured this out, I merely applied my good friend Dan Illowsky&rsquo;s long-standing rule for dealing with MASM:</p>
<p>If the obvious doesn&rsquo;t work (and it usually doesn&rsquo;t), just try everything you can think of, no matter how ridiculous, until you find something that does&mdash;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&rsquo;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&rsquo;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&mdash;gee, it&rsquo;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&rsquo;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 &copy; 2001 Michael Abrash
</div>
</body>
</html>