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

91 lines
7.8 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: Local Optimization</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=07//-->
<!--PAGES=139-141//-->
<!--UNASSIGNED1//-->
<!--UNASSIGNED2//--></HEAD><BODY LINK=#0000FF ALINK=#000099 VLINK=#0000FF BGCOLOR=#FFFFFF>
<CENTER>
<TABLE BORDER>
<TR>
<TD><A HREF="07-01.html">Previous</A></TD>
<TD><A HREF="index.html">Table of Contents</A></TD>
<TD><A HREF="07-03.html">Next</A></TD>
</TR>
</TABLE>
</CENTER>
<P><BR></P>
<P>By the way, don&#146;t fall victim to the lures of <B>JCXZ</B> and do something like this:</P>
<!-- CODE SNIP //-->
<PRE>
and cx,ofh ;Isolate the desired field
jcxz SkipLoop ;If field is 0, don&#146;t bother
</PRE>
<!-- END CODE SNIP //-->
<P>The <B>AND</B> instruction has already set the Zero flag, so this</P>
<!-- CODE SNIP //-->
<PRE>
and cx,0fh ;Isolate the desired field
jz SkipLoop ;If field is 0, don&#146;t bother
</PRE>
<!-- END CODE SNIP //-->
<P>will do just fine and is faster on all processors. Use <B>JCXZ</B> only when the Zero flag isn&#146;t already set to reflect the status of CX.</P>
<H3><A NAME="Heading4"></A><FONT COLOR="#000077">The Lessons of LOOP and JCXZ</FONT></H3>
<P>What can we learn from <B>LOOP</B> and <B>JCXZ?</B> First, that a single instruction that is intended to do a complex task is not necessarily faster than several instructions that together do the same thing. Second, that the relative merits of instructions and optimization rules vary to a surprisingly large degree across the x86 family.</P>
<P>In particular, if you&#146;re going to write 386 protected mode code, which will run only on the 386, 486, and Pentium, you&#146;d be well advised to rethink your use of the more esoteric members of the x86 instruction set. <B>LOOP, JCXZ,</B> the various accumulator-specific instructions, and even the string instructions in many circumstances no longer offer the advantages they did on the 8088. Sometimes they&#146;re just not any faster than more general instructions, so they&#146;re not worth going out of your way to use; sometimes, as with <B>LOOP,</B> they&#146;re actually slower, and you&#146;d do well to avoid them altogether in the 386/486 world. Reviewing the instruction cycle times in the MASM or TASM manuals, or looking over the cycle times in Intel&#146;s literature, is a good place to start; published cycle times are closer to actual execution times on the 386 and 486 than on the 8088, and are reasonably reliable indicators of the relative performance levels of x86 instructions.</P>
<H4 ALIGN="LEFT"><A NAME="Heading5"></A><FONT COLOR="#000077">Avoiding LOOPS of Any Stripe</FONT></H4>
<P>Cycle counting and directly substituting instructions (<B>DEC CX/JNZ</B> for <B>LOOP,</B> for example) are techniques that belong at the lowest level of optimization. It&#146;s an important level, but it&#146;s fairly mechanical; once you&#146;ve learned the capabilities and relative performance levels of the various instructions, you should be able to select the best instructions fairly easily. What&#146;s more, this is a task at which compilers excel. What I&#146;m saying is that you shouldn&#146;t get too caught up in counting cycles because that&#146;s a small (albeit important) part of the optimization picture, and not the area in which your greatest advantage lies.</P>
<H3><A NAME="Heading6"></A><FONT COLOR="#000077">Local Optimization</FONT></H3>
<P>One level at which assembly language programming pays off handsomely is that of <I>local optimization;</I> that is, selecting the best <I>sequence</I> of instructions for a task. The key to local optimization is viewing the 80x86 instruction set as a set of building blocks, each with unique characteristics. Your job is to sequence those blocks so that they perform well. It doesn&#146;t matter what the instructions are intended to do or what their names are; all that matters is what they <I>do.</I></P>
<P>Our discussion of <B>LOOP</B> versus <B>DEC/JNZ</B> is an excellent example of optimization by cycle counting. It&#146;s worth knowing, but once you&#146;ve learned it, you just routinely use <B>DEC/JNZ</B> at the bottom of loops in 386/486-specific code, and that&#146;s that. Besides, you&#146;ll save at most a few cycles each time, and while that helps a little, it&#146;s not going to make all <I>that</I> much difference.</P>
<P>Now let&#146;s step back for a moment, and with no preconceptions consider what the x86 instruction set can do for us. The bulk of the time with both <B>LOOP</B> and <B>DEC/JNZ</B> is taken up by branching, which just happens to be one of the slowest aspects of every processor in the x86 family, and the rest is taken up by decrementing the count register and checking whether it&#146;s zero. There may be ways to perform those tasks a little faster by selecting different instructions, but they can get only so fast, and branching can&#146;t even get all that fast.</P>
<TABLE WIDTH="100%"><TD ALIGN="LEFT" VALIGN="TOP" WIDTH="5%"><IMG SRC="images/07-04i.jpg"><TD ALIGN="LEFT" VALIGN="TOP" WIDTH="95%"><SMALL><I>The trick, then, is not to find the fastest way to decrement a count and branch conditionally, but rather to figure out how to accomplish the same result without decrementing or branching as often. Remember the Kobiyashi Maru problem in</I> Star Trek<I>?The same principle applies here: Redefine the problem to one that offers better solutions.</I></SMALL>
</TABLE>
<P>Consider Listing 7.1, which searches a buffer until either the specified byte is found, a zero byte is found, or the specified number of characters have been checked. Such a function would be useful for scanning up to a maximum number of characters in a zero-terminated buffer. Listing 7.1, which uses <B>LOOP</B> in the main loop, performs a search of the sample string for a period (&#145;.&#146;) in 170 &#181;s on a 20 MHz cached 386.</P>
<P>When the <B>LOOP</B> in Listing 7.1 is replaced with <B>DEC CX/JNZ,</B> performance improves to 168 &#181;s, less than 2 percent faster than Listing 7.1. Actually, instruction fetching, instruction alignment, cache characteristics, or something similar is affecting these results; I&#146;d expect a slightly larger improvement&#151;around 7 percent&#151;but that&#146;s the most that counting cycles could buy us in this case. (All right, already; <B>LOOPNZ</B> could be used at the bottom of the loop, and other optimizations are surely possible, but all that won&#146;t add up to anywhere near the benefits we&#146;re about to see from local optimization, and that&#146;s the whole point.)</P><P><BR></P>
<CENTER>
<TABLE BORDER>
<TR>
<TD><A HREF="07-01.html">Previous</A></TD>
<TD><A HREF="index.html">Table of Contents</A></TD>
<TD><A HREF="07-03.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 -->