148 lines
9 KiB
HTML
148 lines
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=241-243//-->
|
|
<!--UNASSIGNED1//-->
|
|
<!--UNASSIGNED2//--></HEAD><BODY LINK=#0000FF ALINK=#000099 VLINK=#0000FF BGCOLOR=#FFFFFF>
|
|
|
|
<CENTER>
|
|
<TABLE BORDER>
|
|
<TR>
|
|
<TD><A HREF="12-02.html">Previous</A></TD>
|
|
<TD><A HREF="index.html">Table of Contents</A></TD>
|
|
<TD><A HREF="12-04.html">Next</A></TD>
|
|
</TR>
|
|
</TABLE>
|
|
</CENTER>
|
|
<P><BR></P>
|
|
<P>There is, of course, no guarantee that I’m entirely correct about the optimizations discussed in this chapter. Without knowing the internals of the 486, all I can do is time code and make inferences from the results; I invite you to deduce your own rules and cross-check them against mine. Also, most likely there are other optimizations that I’m unaware of. If you have further information on these or any other undocumented optimizations, please write and let me know. And, of course, if anyone from Intel is reading this and wants to give us the gospel truth, please do!
|
|
</P>
|
|
<H4 ALIGN="LEFT"><A NAME="Heading8"></A><FONT COLOR="#000077">Stack Addressing and Address Pipelining</FONT></H4>
|
|
<P>Rule #2A: Rule #2 sometimes, but not always, applies to the stack pointer when it is implicitly used to point to memory.
|
|
</P>
|
|
<P>Intel states that the stack pointer is an implied destination register for <B>CALL</B>, <B>ENTER</B>, <B>LEAVE</B>, <B>RET</B>, <B>PUSH</B>, and <B>POP</B> (which alter (E)SP), and that it is the implied base addressing register for <B>PUSH</B>, <B>POP</B>, and <B>RET</B> (which use (E)SP to address memory). Intel then implies that the aforementioned addressing pipeline penalty is incurred whenever the stack pointer is used as a destination by one of the first set of instructions and is then immediately used to address memory by one of the second set. This raises the specter of unpleasant programming contortions such as intermixing <B>PUSH</B>es and <B>POP</B>s with other instructions to avoid interrupting the addressing pipeline. Fortunately, matters are actually not so grim as Intel’s documentation would indicate; my tests indicate that the addressing pipeline penalty pops up only spottily when the stack pointer is involved.</P>
|
|
<P>For example, you’d certainly expect a sequence such as</P>
|
|
<!-- CODE SNIP //-->
|
|
<PRE>
|
|
:
|
|
pop ax
|
|
ret
|
|
pop ax
|
|
et
|
|
:
|
|
</PRE>
|
|
<!-- END CODE SNIP //-->
|
|
<P>to exhibit the addressing pipeline interruption phenomenon (SP is both destination and addressing register for both instructions, according to Intel), but this code runs in six cycles per <B>POP/RET</B> pair, matching the official execution times exactly. Likewise, a sequence like</P>
|
|
<!-- CODE SNIP //-->
|
|
<PRE>
|
|
pop dx
|
|
pop cx
|
|
pop bx
|
|
pop ax
|
|
</PRE>
|
|
<!-- END CODE SNIP //-->
|
|
<P>runs in one cycle per instruction, just as it should.
|
|
</P>
|
|
<P>On the other hand, performing arithmetic directly on SP as an <I>explicit</I> destination—for example, to deallocate local variables—and then using <B>PUSH</B>, <B>POP</B>, or <B>RET</B>, definitely can interrupt the addressing pipeline. For example</P>
|
|
<!-- CODE SNIP //-->
|
|
<PRE>
|
|
add sp,10h
|
|
ret
|
|
</PRE>
|
|
<!-- END CODE SNIP //-->
|
|
<P>loses two cycles because SP is the explicit destination of one instruction and then the implied addressing register for the next, and the sequence
|
|
</P>
|
|
<!-- CODE SNIP //-->
|
|
<PRE>
|
|
add sp,10h
|
|
pop ax
|
|
</PRE>
|
|
<!-- END CODE SNIP //-->
|
|
<P>loses two cycles for the same reason.
|
|
</P>
|
|
<P>I certainly haven’t tried all possible combinations, but the results so far indicate that the stack pointer incurs the addressing pipeline penalty only if (E)SP is the <I>explicit</I> destination of one instruction and is then used by one of the two following instructions to address memory. So, for instance, SP isn’t the explicit operand of <B>POP AX—</B>AX is—and no cycles are lost if <B>POP AX</B> is followed by <B>POP</B> or <B>RET</B>. Happily, then, we need not worry about the sequence in which we use <B>PUSH</B> and <B>POP</B>. However, adding to, moving to, or subtracting from the stack pointer should ideally be done at least two cycles before <B>PUSH</B>, <B>POP</B>, <B>RET</B>, or any other instruction that uses the stack pointer to address memory.</P>
|
|
<H4 ALIGN="LEFT"><A NAME="Heading9"></A><FONT COLOR="#000077">Problems with Byte Registers</FONT></H4>
|
|
<P>There are two ways to lose cycles by using byte registers, and neither of them is documented by Intel, so far as I know. Let’s start with the lesser and simpler of the two.
|
|
</P>
|
|
<P>Rule #3: Do not load a byte portion of a register during one instruction, then use that register in its entirety as a source register during the next instruction.</P>
|
|
<P>So, for example, it would be a bad idea to do this</P>
|
|
<!-- CODE SNIP //-->
|
|
<PRE>
|
|
mov ah,o
|
|
:
|
|
mov cx,[MemVar1]
|
|
mov al,[MemVar2]
|
|
add cx,ax
|
|
</PRE>
|
|
<!-- END CODE SNIP //-->
|
|
<P>because AL is loaded by one instruction, then AX is used as the source register for the next instruction. A cycle can be saved simply by rearranging the instructions so that the byte register load isn’t immediately followed by the word register usage, like so:
|
|
</P>
|
|
<!-- CODE SNIP //-->
|
|
<PRE>
|
|
mov ah,o
|
|
:
|
|
mov al,[MemVar2]
|
|
mov cx,[MemVar1]
|
|
add cx,ax
|
|
</PRE>
|
|
<!-- END CODE SNIP //-->
|
|
<P>Strange as it may seem, this rule is neither arbitrary nor nonsensical. Basically, when a byte destination register is part of a word source register for the next instruction, the 486 is unable to directly use the result from the first instruction as the source for the second instruction, because only part of the register required by the second instruction is contained in the first instruction’s result. The full, updated register value must be read from the register file, and that value can’t be read out until the result from the first instruction has been written <I>into</I> the register file, a process that takes an extra cycle. I’m not going to explain this in great detail because it’s not important that you understand why this rule exists (only that it <I>does</I> in fact exist), but it is an interesting window on the way the 486 works.</P>
|
|
<P>In case you’re curious, there’s no such penalty for the typical <B>XLAT</B> sequence like</P>
|
|
<!-- CODE SNIP //-->
|
|
<PRE>
|
|
mov bx,offset MemTable
|
|
:
|
|
mov al,[si]
|
|
xlat
|
|
</PRE>
|
|
<!-- END CODE SNIP //-->
|
|
<P>even though AL must be converted to a word by <B>XLAT</B> before it can be added to BX and used to address memory. In fact, none of the penalties mentioned in this chapter apply to <B>XLAT</B>, apparently because <B>XLAT</B> is so slow—4 cycles—that it gives the 486 time to perform addressing calculations during the course of the instruction.</P>
|
|
<TABLE WIDTH="100%"><TD ALIGN="LEFT" VALIGN="TOP" WIDTH="5%"><IMG SRC="images/12-04i.jpg"><TD ALIGN="LEFT" VALIGN="TOP" WIDTH="95%"><SMALL><I>While it’s nice that <B>XLAT</B> doesn’t suffer from the various 486 addressing penalties, the reason for that is basically that <B>XLAT</B> is slow, so there’s still no compelling reason to use <B>XLAT</B> on the 486.</I></SMALL>
|
|
</TABLE>
|
|
<P>In general, penalties for interrupting the 486’s pipeline apply primarily to the fast core instructions of the 486, most notably register-only instructions and <B>MOV</B>, although arithmetic and logical operations that access memory are also often affected. I don’t know all the performance dependencies, and I don’t plan to; figuring all of them out would be a big, boring job of little value. Basically, on the 486 you should concentrate on using those fast core instructions when performance matters, and all the rules I’ll discuss do indeed apply to those instructions.</P><P><BR></P>
|
|
<CENTER>
|
|
<TABLE BORDER>
|
|
<TR>
|
|
<TD><A HREF="12-02.html">Previous</A></TD>
|
|
<TD><A HREF="index.html">Table of Contents</A></TD>
|
|
<TD><A HREF="12-04.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 © 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 -->
|
|
|
|
|