91 lines
8.8 KiB
HTML
91 lines
8.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: The Game of Life</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=17//-->
|
|
<!--PAGES=338-340//-->
|
|
<!--UNASSIGNED1//-->
|
|
<!--UNASSIGNED2//--></HEAD><BODY LINK=#0000FF ALINK=#000099 VLINK=#0000FF BGCOLOR=#FFFFFF>
|
|
|
|
<CENTER>
|
|
<TABLE BORDER>
|
|
<TR>
|
|
<TD><A HREF="17-05.html">Previous</A></TD>
|
|
<TD><A HREF="index.html">Table of Contents</A></TD>
|
|
<TD><A HREF="17-07.html">Next</A></TD>
|
|
</TR>
|
|
</TABLE>
|
|
</CENTER>
|
|
<P><BR></P>
|
|
<P>We’re still not ready for assembly, though; what we need is a new perspective that lends itself to vastly better performance in C<SMALL>++</SMALL>. The Life program in the next section is <I>three to seven times</I> faster than Listing 17.4—and it’s still in C<SMALL>++</SMALL>.</P>
|
|
<P>How is this possible? Here are some hints:</P>
|
|
<DL>
|
|
<DD><B>•</B> After a few dozen generations, most of the cellmap consists of cells in the off state.
|
|
<DD><B>•</B> There are many possible cellmap representations other than one bit-per-pixel.
|
|
<DD><B>•</B> Cells change state relatively infrequently.
|
|
</DL>
|
|
<H3><A NAME="Heading8"></A><FONT COLOR="#000077">Bringing In the Right Brain</FONT></H3>
|
|
<P>In the previous section, we saw how a C<SMALL>++</SMALL> program could be sped up about eight times simply by rearranging the data and code in straightforward ways. Now we’re going to see how right-brain non-linear optimization can speed things up by another four times—and make the code <I>simpler.</I></P>
|
|
<P>Now <I>that’s</I> Zen code optimization.</P>
|
|
<P>I have two objectives to achieve in the remainder of this chapter. First, I want to show that optimization consists of many levels, from assembly language up to conceptual design, and that assembly language kicks in pretty late in the optimization process. Second, I want to encourage you to saturate your brain with everything you know about any particular optimization problem, then make space for your right brain to solve the problem.</P>
|
|
<H4 ALIGN="LEFT"><A NAME="Heading9"></A><FONT COLOR="#000077">Re-Examining the Task</FONT></H4>
|
|
<P>Earlier in this chapter, we looked at a straightforward Game of Life implementation, then increased performance considerably by making the implementation a little less abstract and a little less general. We made a small change to the cellmap format, adding padding bytes off the edges so that pointer arithmetic would always work, but the major optimizations were moving the critical code into a single loop and using pointers rather than member functions whenever possible. In other words, we took what we already knew and made it more efficient.
|
|
</P>
|
|
<P>Now it’s time to re-examine the nature of this programming task from the ground up, looking for things that we <I>don’t</I> yet know. Let’s take a moment to review what the Game of Life consists of. The basic task is evolving a new generation, and that’s done by looking at the number of “on” neighbors a cell has and the cell’s own state. If a cell is on, and two or three neighbors are on, then the cell stays on; otherwise, an on-cell is turned off. If a cell is off and exactly three neighbors are on, then the cell is turned on; otherwise, an off-cell stays off. That’s all there is to it. As any fool can see, the trick is to arrange things so that we can count neighbors and check the cell state as quickly as possible. Large lookup tables, oddly encoded cellmaps, and lots of bit-twiddling assembly code spring to mind as possible approaches. Can’t you just feel your adrenaline start to pump?</P>
|
|
<TABLE WIDTH="100%"><TD VALIGN="TOP" ALIGN="LEFT" WIDTH="5%"><IMG SRC="images/17-04i.jpg"><TD VALIGN="TOP" ALIGN="LEFT" WIDTH="95%"><I><SMALL>Relax. Step back. Try to divine the true nature of the problem. The object is not to count neighbors and check cell states as quickly as possible; that’s just one possible implementation. The object is to determine when a cell’s state must be changed and to change it appropriately, and that’s what we need to do as quickly as possible.</SMALL></I>
|
|
</TABLE>
|
|
<P>What difference does that new perspective make? Let’s approach it this way. What does a typical cellmap look like? As it happens, after a few generations, the vast majority of cells are off. In fact, the vast majority of cells are not only off but are entirely surrounded by off-cells. Also, cells change state infrequently; in any given generation after the first few, most cells remain in the same state as in the previous generation.
|
|
</P>
|
|
<P>Do you see where I’m heading? Do you hear a whisper of inspiration from your right brain? The original implementation stored cell states as 1-bits (on), or 0-bits (off). For each generation and for each cell, it counted the states of the eight neighbors, for an average of eight operations per cell per generation. Suppose, now, that on average 10 percent of cells change state from one generation to the next. (The actual percentage is even lower, but this will do for illustration.) Suppose also that we change the cell map format to store a byte rather than a bit for each cell, with the byte storing not only the cell state but also the count of neighboring on-cells for that cell. Figure 17.3 shows this format. Then, rather than counting neighbors each time, we could just look at the neighbor count in the cell and operate directly from that.</P>
|
|
<P>But what about the overhead needed to maintain the neighbor counts? Well, each time a cell changes state, eight operations would be needed to update the counts in the eight neighboring cells. But this happens only once every ten cells, on average—so the cost of this approach is only one-tenth that of the original approach!</P>
|
|
<P><I>Know your data.</I></P>
|
|
<P><A NAME="Fig3"><!-- </A><A HREF="javascript:displayWindow('images/17-03.jpg',405,113 )"> --><IMG SRC="images/17-03.jpg"><BR><!-- </A>
|
|
<BR><A HREF="javascript:displayWindow('images/17-03.jpg',405,113)"> --><FONT COLOR="#000077"><B>Figure 17.3</B></FONT></A> <I>New cell format.</I>
|
|
</P>
|
|
<H4 ALIGN="LEFT"><A NAME="Heading10"></A><FONT COLOR="#000077">Acting on What We Know</FONT></H4>
|
|
<P>Once we’ve changed the cellmap format to store neighbor counts as well as states, with a byte for each cell, we can get another performance boost by again examining what we know about our data. I said earlier that most cells are off during any given generation. This means that most cells have no neighbors that are on. Since the cell map representation for an off-cell that has no neighbors is a zero byte, we can skip over scads of unchanged cells at a pop simply by scanning for non-zero bytes. This is much faster than explicitly testing cell states and neighbor counts, and lends itself beautifully to assembly language implementation as <B>REPZ SCASB</B> or (with a little cleverness) <B>REPZ SCASW.</B> (Unfortunately, there’s no C library function that can scan memory for the next byte that’s non-zero.)</P>
|
|
<P>Listing 17.5 is a Game of Life implementation that uses the neighbor-count cell map format and scans for non-zero bytes. On a 20 MHz 386, Listing 17.5 is about 4.5 times faster at calculating generations (that is, the generation engine is 4.5 times faster; I’m ignoring the time consumed by drawing and text display) than Listing 17.4, which is no slouch. On a 33 MHz 486, Listing 17.5 is about 3.5 times faster than Listing 17.4. This is true even though Listing 17.5 must be compiled using the large model. Imagine that—getting a four times speed-up while switching from the small model to the large model!</P><P><BR></P>
|
|
<CENTER>
|
|
<TABLE BORDER>
|
|
<TR>
|
|
<TD><A HREF="17-05.html">Previous</A></TD>
|
|
<TD><A HREF="index.html">Table of Contents</A></TD>
|
|
<TD><A HREF="17-07.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 -->
|
|
|
|
|