abrash-black-book/12-04.html
2013-12-30 12:21:49 +11:00

159 lines
8.9 KiB
HTML

<HTML>
<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: Pushing the 486</TITLE>
<!-- HEADER -->
<!-- Empty Reference Subhead -->
<!--ISBN=1576101746//-->
<!--TITLE=Michael Abrash's Graphics Programming Black Book Special Edition//-->
<!--AUTHOR=Michael Abrash//-->
<!--PUBLISHER=The Coriolis Group, Inc.//-->
<!--CHAPTER=12//-->
<!--PAGES=243-246//-->
<!--UNASSIGNED1//-->
<!--UNASSIGNED2//--></HEAD><BODY LINK=#0000FF ALINK=#000099 VLINK=#0000FF BGCOLOR=#FFFFFF>
<CENTER>
<TABLE BORDER>
<TR>
<TD><A HREF="12-03.html">Previous</A></TD>
<TD><A HREF="index.html">Table of Contents</A></TD>
<TD><A HREF="13-01.html">Next</A></TD>
</TR>
</TABLE>
</CENTER>
<P><BR></P>
<P>You don&#146;t need to understand every corner of the 486 universe unless you&#146;re a diehard ASMhead who does this stuff for fun. Just learn enough to be able to speed up the key portions of your programs, and spend the rest of your time on a fast design and overall implementation.
</P>
<H4 ALIGN="LEFT"><A NAME="Heading10"></A><FONT COLOR="#000077">More Fun with Byte Registers</FONT></H4>
<P>Rule #4: Don&#146;t load <I>any</I> byte register exactly 2 cycles before using <I>any</I> register to address memory.</P>
<P>This, the last of this chapter&#146;s rules, is the strangest of the lot. If any byte register is loaded, and then two cycles later any register is used to point to memory, one cycle is lost. So, for example, this code</P>
<!-- CODE SNIP //-->
<PRE>
mov al,bl
mov cx,dx
mov si,[di]
</PRE>
<!-- END CODE SNIP //-->
<P>takes four rather than the expected three cycles to execute. Note that it is <I>not</I> required that the byte register be part of the register used to address memory; any byte register will do the trick.</P>
<P>Worse still, loading byte registers both one and two cycles before a register is used to address memory costs two cycles, as in</P>
<!-- CODE SNIP //-->
<PRE>
mov bl,al
mov cl,3
mov bx,[si]
</PRE>
<!-- END CODE SNIP //-->
<P>which takes five rather than three cycles to run. However, there is <I>no</I> penalty if a byte register is loaded one cycle but not two cycles before a register is used to address memory. Therefore,</P>
<!-- CODE SNIP //-->
<PRE>
mov cx,3
mov dl,al
mov si,[bx]
</PRE>
<!-- END CODE SNIP //-->
<P>runs in the expected three cycles.
</P>
<P>In truth, I do not know why this happens. Clearly, it has something to do with interrupting the start of the addressing pipeline, and I have my theories about how this works, but at this point they&#146;re pure speculation. Whatever the reason for this rule, ignorance of it&#151;and of its interaction with the other rules&#151;could lead to considerable performance loss in seemingly air-tight code. For instance, a casual observer would expect the following code to run in 3 cycles:</P>
<!-- CODE SNIP //-->
<PRE>
mov bx,offset MemVar
mov cl,al
mov ax,[bx]
</PRE>
<!-- END CODE SNIP //-->
<P>A more sophisticated programmer would expect to lose one cycle, because BX is loaded two cycles before being used to address memory. In fact, though, this code takes 5 cycles&#151;2 cycles, or 67 percent, longer than normal. Why? Well, under normal conditions, loading a byte register&#151;CL in this case&#151;one cycle before using a register to address memory produces no penalty; loading 2 cycles ahead is the only case that normally incurs a penalty. However, think of Rule #4 as meaning that loading a byte register disrupts the memory addressing pipeline as it starts up. Viewed that way, we can see that <B>MOV BX,OFFSET MemVar</B> interrupts the addressing pipeline, forcing it to start again, and then, presumably, <B>MOV CL,AL</B> interrupts the pipeline again because the pipeline is now on its first cycle: the one that loading a byte register can affect.</P>
<TABLE WIDTH="100%"><TD ALIGN="LEFT" VALIGN="TOP" WIDTH="5%"><IMG SRC="images/12-05i.jpg"><TD ALIGN="LEFT" VALIGN="TOP" WIDTH="95%"><SMALL><I>I know&#151;it seems awfully complicated. It isn&#146;t, really. Generally, try not to use byte destinations exactly two cycles before using a register to address memory, and try not to load a register either one or two cycles before using it to address memory, and you&#146;ll be fine.</I></SMALL>
</TABLE>
<H4 ALIGN="LEFT"><A NAME="Heading11"></A><FONT COLOR="#000077">Timing Your Own 486 Code</FONT></H4>
<P>In case you want to do some 486 performance analysis of your own, let me show you how I arrived at one of the above conclusions; at the same time, I can warn you of the timing hazards of the cache. Listings 12.1 and 12.2 show the code I ran through the Zen timer in order to establish the effects of loading a byte register before using a register to address memory. Listing 12.1 ran in 120 &#181;s on a 33 MHz 486, or 4 cycles per repetition (120 &#181;s/1000 repetitions = 120 ns per repetition; 120 ns per repetition/30 ns per cycle = 4 cycles per repetition); Listing 12.2 ran in 90 &#181;s, or 3 cycles, establishing that loading a byte register costs a cycle only when it&#146;s performed exactly 2 cycles before addressing memory.
</P>
<P><B>LISTING 12.1 LST12-1.ASM</B></P>
<!-- CODE //-->
<PRE>
; Measures the effect of loading a byte register 2 cycles before
; using a register to address memory.
mov bp,2 ;run the test code twice to make sure
; it&#146;s cached
sub bx,bx
CacheFillLoop:
call ZTimerOn ;start timing
rept 1000
mov dl,cl
nop
mov ax,[bx]
endm
call ZTimerOff ;stop timing
dec bp
jz Done
jmp CacheFillLoop
Done:
</PRE>
<!-- END CODE //-->
<P><B>LISTING 12.2 LST12-2.ASM</B></P>
<!-- CODE //-->
<PRE>
; Measures the effect of loading a byte register 1 cycle before
; using a register to address memory.
mov bp,2 ;run the test code twice to make sure
; it&#146;s cached
sub bx,bx
CacheFillLoop:
call ZTimerOn ;start timing
rept 1000
nop
mov dl,cl
mov ax,[bx]
endm
call ZTimerOff ;stop timing
dec bp
jz Done
jmp CacheFillLoop
Done:
</PRE>
<!-- END CODE //-->
<P>Note that Listings 12.1 and 12.2 each repeat the timing of the code under test a second time, to make sure that the instructions are in the cache on the second pass, the one for which results are displayed. Also note that the code is less than 8K in size, so that it can all fit in the 486&#146;s 8K internal cache. If I double the <B>REPT</B> value in Listing 12.2 to 2,000, making the test code larger than 8K, the execution time more than doubles to 224 &#181;s, or 3.7 cycles per repetition; the extra seven-tenths of a cycle comes from fetching non-cached instruction bytes.</P>
<TABLE WIDTH="100%"><TD ALIGN="LEFT" VALIGN="TOP" WIDTH="5%"><IMG SRC="images/12-06i.jpg"><TD ALIGN="LEFT" VALIGN="TOP" WIDTH="95%"><SMALL><I>Whenever you see non-integral timing results of this sort, it&#146;s a good bet that the test code or data isn&#146;t cached.</I></SMALL>
</TABLE>
<H3><A NAME="Heading12"></A><FONT COLOR="#000077">The Story Continues</FONT></H3>
<P>There&#146;s certainly plenty more 486 lore to explore, including the 486&#146;s unique prefetch queue, more optimization rules, branching optimizations, performance implications of the cache, the cost of cache misses for reads, and the implications of cache write-through for writes. Nonetheless, we&#146;ve covered quite a bit of ground in this chapter, and I trust you&#146;ve gotten a feel for the considerable extent to which 486 optimization differs from what you&#146;re used to. Odd as 486 optimization is, though, it&#146;s well worth mastering, for the 486 is, at its best, so staggeringly fast that carefully crafted 486 code can do more than twice as much per cycle as the best 386 code&#151;which makes it perhaps 50 times as fast as optimized code for the original PC.
</P>
<P>Sometimes it <I>is</I> hard to believe we&#146;re still in Kansas!</P><P><BR></P>
<CENTER>
<TABLE BORDER>
<TR>
<TD><A HREF="12-03.html">Previous</A></TD>
<TD><A HREF="index.html">Table of Contents</A></TD>
<TD><A HREF="13-01.html">Next</A></TD>
</TR>
</TABLE>
</CENTER>
<hr width="90%" size="1" noshade>
<div align="center">
<font face="Verdana,sans-serif" size="1">Graphics Programming Black Book &copy; 2001 Michael Abrash</font>
</div>
<!-- all of the reference materials (books) have the footer and subfoot reveresed -->
<!-- reference_subfoot = footer -->
<!-- reference_footer = subfoot -->
<!-- BEGIN SUB FOOTER -->
</BODY>
</HTML>
<!-- END FOOTER -->