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

88 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: Crossing the Border</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=05//-->
<!--PAGES=115-118//-->
<!--UNASSIGNED1//-->
<!--UNASSIGNED2//--></HEAD><BODY LINK=#0000FF ALINK=#000099 VLINK=#0000FF BGCOLOR=#FFFFFF>
<CENTER>
<TABLE BORDER>
<TR>
<TD><A HREF="05-01.html">Previous</A></TD>
<TD><A HREF="index.html">Table of Contents</A></TD>
<TD><A HREF="05-03.html">Next</A></TD>
</TR>
</TABLE>
</CENTER>
<P><BR></P>
<P>We could put a zero byte at the end of our buffer to allow <B>strstr()</B> to work, but why bother? The <B>strstr()</B> function must spend time either checking for the end of the string being searched or determining the length of that string&#151;wasted effort given that we already know exactly how long our search buffer is. Even if a given <B>strstr()</B> implementation is well-written, its performance will suffer, at least for our application, from unnecessary overhead.</P>
<TABLE WIDTH="100%"><TD ALIGN="LEFT" VALIGN="TOP" WIDTH="5%"><IMG SRC="images/05-02i.jpg"><TD ALIGN="LEFT" VALIGN="TOP" WIDTH="95%"><SMALL><I>This illustrates why you shouldn&#146;t think of C/C<SMALL>&#43;&#43;</SMALL> library functions as black boxes; understand what they do and try to figure out how they do it, and relate that to their performance in the context you&#146;re interested in.</I></SMALL>
</TABLE>
<H3><A NAME="Heading5"></A><FONT COLOR="#000077">Brute-Force Techniques</FONT></H3>
<P>Given that no C/C<SMALL>&#43;&#43;</SMALL> library function meets our needs precisely, an obvious alternative approach is the brute-force technique that uses <B>memcmp()</B> to compare <I>every</I> potential matching location in the buffer to the string we&#146;re searching for, as illustrated in Figure 5.1.</P>
<P>By the way, we could, of course, use our own code, working with pointers in a loop, to perform the comparison in place of <B>memcmp()</B>. But <B>memcmp()</B> will almost certainly use the very fast <B>REPZ CMPS</B> instruction. However, <I>never assume!</I> It wouldn&#146;t hurt to use a debugger to check out the actual machine-code implementation of <B>memcmp()</B> from your compiler. If necessary, you could always write your own assembly language implementation of <B>memcmp()</B>.</P>
<P><A NAME="Fig1"><!-- </A><A HREF="javascript:displayWindow('images/05-01.jpg',415,211 )"> --><IMG SRC="images/05-01.jpg"><BR><!-- </A>
<BR><A HREF="javascript:displayWindow('images/05-01.jpg',415,211)"> --><FONT COLOR="#000077"><B>Figure 5.1</B></FONT></A>&nbsp;&nbsp;<I>The brute-force searching technique.</I>
</P>
<P>Invoking <B>memcmp()</B> for each potential match location works, but entails considerable overhead. Each comparison requires that parameters be pushed and that a call to and return from <B>memcmp()</B> be performed, along with a pass through the comparison loop. Surely there&#146;s a better way!</P>
<P>Indeed there is. We can eliminate most calls to <B>memcmp()</B> by performing a simple test on each potential match location that will reject most such locations right off the bat. We&#146;ll just check whether the first character of the potentially matching buffer location matches the first character of the string we&#146;re searching for. We could make this check by using a pointer in a loop to scan the buffer for the next match for the first character, stopping to check for a match with the rest of the string <I>only</I> when the first character matches, as shown in Figure 5.2.</P>
<H3><A NAME="Heading6"></A><FONT COLOR="#000077">Using memchr()</FONT></H3>
<P>There&#146;s yet a better way to implement this approach, however. Use the <B>memchr()</B> function, which does nothing more or less than find the next occurrence of a specified character in a fixed-length buffer (presumably by using the extremely efficient <B>REPNZ SCASB</B> instruction, although again it wouldn&#146;t hurt to check). By using <B>memchr()</B> to scan for potential matches that can then be fully tested with <B>memcmp()</B>, we can build a highly efficient search engine that takes good advantage of the information we have about the buffer being searched and the string we&#146;re searching for. Our engine also relies heavily on repeated string instructions, assuming that the <B>memchr()</B> and <B>memcmp()</B> library functions are properly coded.</P>
<P><A NAME="Fig2"><!-- </A><A HREF="javascript:displayWindow('images/05-02.jpg',409,241 )"> --><IMG SRC="images/05-02.jpg"><BR><!-- </A>
<BR><A HREF="javascript:displayWindow('images/05-02.jpg',409,241)"> --><FONT COLOR="#000077"><B>Figure 5.2</B></FONT></A>&nbsp;&nbsp;<I>The faster string-searching technique.</I>
</P>
<P>We&#146;re going to go with the this approach in our file-searching program; the only trick lies in deciding how to integrate this approach with restartable blocks in order to search through files larger than our buffer. This certainly isn&#146;t the fastest-possible searching algorithm; as one example, the Boyer-Moore algorithm, which cleverly eliminates many buffer locations as potential matches in the process of checking preceding locations, can be considerably faster. However, the Boyer-Moore algorithm is quite complex to understand and implement, and would distract us from our main focus, restartable blocks, so we&#146;ll save it for a later chapter (Chapter 14, to be precise). Besides, I suspect you&#146;ll find the approach we&#146;ll use to be fast enough for most purposes.
</P>
<P>Now that we&#146;ve selected a searching approach, let&#146;s integrate it with file handling and searching through multiple blocks. In other words, let&#146;s make it restartable.</P>
<H4 ALIGN="LEFT"><A NAME="Heading7"></A><FONT COLOR="#000077">Making a Search Restartable</FONT></H4>
<P>As it happens, there&#146;s no great trick to putting the pieces of this search program together. Basically, we&#146;ll read in a buffer of data (we&#146;ll work with 16K at a time to avoid signed overflow problems with integers), search it for a match with the <B>memchr()/memcmp()</B> engine described, and exit with a &#147;string found&#148; response if the desired string is found.</P>
<P>Otherwise, we&#146;ll load in another buffer full of data from the file, search it, and so on. The only trick lies in handling potentially matching sequences in the file that start in one buffer and end in the next&#151;that is, sequences that span buffers. We&#146;ll handle this by copying the unchecked bytes at the end of one buffer to the start of the next and reading that many fewer bytes the next time we fill the buffer.</P>
<P>The exact number of bytes to be copied from the end of one buffer to the start of the next is the length of the searched-for string minus 1, since that&#146;s how many bytes at the end of the buffer can&#146;t be checked as possible matches (because the check would run off the end of the buffer).</P>
<P>That&#146;s really all there is to it. Listing 5.1 shows the file-searching program. As you can see, it&#146;s not particularly complex, although a few fairly opaque lines of code are required to handle merging the end of one block with the start of the next. The code that searches a single block&#151;the function <B>SearchForString()&#151;</B>is simple and compact (as it should be, given that it&#146;s by far the most heavily-executed code in the listing).</P>
<P>Listing 5.1 nicely illustrates the core concept of restartable blocks: Organize your program so that you can do your processing within each block as fast as you could if there were only one block&#151;which is to say at top speed&#151;and make your blocks as large as possible in order to minimize the overhead associated with going from one block to the next.</P><P><BR></P>
<CENTER>
<TABLE BORDER>
<TR>
<TD><A HREF="05-01.html">Previous</A></TD>
<TD><A HREF="index.html">Table of Contents</A></TD>
<TD><A HREF="05-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 -->