Initial conversion to markdown
This commit is contained in:
parent
a96a114403
commit
5e10794a24
362 changed files with 67353 additions and 0 deletions
174
16-08.md
Normal file
174
16-08.md
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
------------------------ --------------------------------- --------------------
|
||||
[Previous](16-07.html) [Table of Contents](index.html) [Next](17-01.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
**Listing 16.7 L16-7.ASM**
|
||||
|
||||
ScanLoop:
|
||||
lodsw ;get the next 2 bytes (AL = first, AH = 2nd)
|
||||
xlat ;look up first’s char/not status
|
||||
xor dl,al ;see if there’s a new char/not status
|
||||
add di,dx ;we add 1 for each char/not transition
|
||||
mov dl,al
|
||||
mov al,ah ;look at the second byte
|
||||
xlat ;look up its char/not status
|
||||
xor dl,al ;see if there’s a new char/not status
|
||||
add di,dx ;we add 1 for each char/not transition
|
||||
mov dl,al
|
||||
dec dx
|
||||
jnz ScanLoop
|
||||
|
||||
|
||||
John later divides the transition count by two to get the word count.
|
||||
(Food for thought: It’s also possible to use **CMP** and **ADC** to
|
||||
detect words without branching.)
|
||||
|
||||
John’s approach makes it clear that word-counting is nothing more than a
|
||||
fairly simple state machine. The interesting part, of course, is
|
||||
building the fastest state machine.
|
||||
|
||||
#### Level 3: Breakthrough {#Heading12}
|
||||
|
||||
The boundaries between the levels of optimization are not sharply
|
||||
defined. In a sense, level 3 optimization is just like levels 1 and 2,
|
||||
but more so. At level 3, one takes whatever level 2 perspective seems
|
||||
most promising, and implements it as efficiently as possible on the x86.
|
||||
Even more than at level 2, at level 3 this means breaking out of
|
||||
familiar patterns of thinking.
|
||||
|
||||
In the case of word counting, level 3 means building a table-driven
|
||||
state machine dedicated to processing a buffer of bytes into a count of
|
||||
words with a minimum of branching. This level of optimization strips
|
||||
away many of the abstractions we usually use in coding, such as loops,
|
||||
tests, and named variables—look back to Listing 16.5, and you’ll see
|
||||
what I mean. Only a few people reached this level, and I don’t think any
|
||||
of them did it without long, hard thinking; David Stafford’s final entry
|
||||
(that is, the one I present as Listing 16.5) was *at least* the fifth
|
||||
entry he sent me.
|
||||
|
||||
The key concept at level 3 is the use of a massive (64K) lookup table
|
||||
that processes byte sequences directly into word-count actions. With
|
||||
such a table, it’s possible to look up the appropriate action for two
|
||||
bytes simultaneously in just a few instructions; next, I’m going to look
|
||||
at the inspired and highly unusual way that David’s code, shown in
|
||||
Listing 16.5, does exactly that. (Before assembling Listing 16.5, you
|
||||
must run the C code in Listing 16.8, to generate an include file
|
||||
defining the 64K lookup table. When you assemble Listing 16.5, TASM will
|
||||
report a “location counter overflow” warning; ignore it.)
|
||||
|
||||
**LISTING 16.8 MAKETAB.C**
|
||||
|
||||
// MAKETAB.C — Build QSCAN3.INC for QSCAN3.ASM
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define ChType( c ) (((c) & 0x7f) == ‘\’’ || isalnum((c) & 0x7f))
|
||||
|
||||
int NoCarry[ 4 ] = { 0, 0x80, 1, 0x80 };
|
||||
int Carry[ 4 ] = { 1, 0x81, 1, 0x80 };
|
||||
|
||||
void main( void )
|
||||
{
|
||||
int ahChar, alChar, i;
|
||||
FILE *t = fopen( “QSCAN3.INC”, “wt” );
|
||||
|
||||
printf( “Building table. Please wait...” );
|
||||
|
||||
for( ahChar = 0; ahChar < 128; ahChar++ )
|
||||
{
|
||||
for( alChar = 0; alChar < 256; alChar++ )
|
||||
{
|
||||
i = ChType( alChar ) * 2 + ChType( ahChar );
|
||||
|
||||
if( alChar % 8 == 0 ) fprintf( t, “\ndb %02Xh”, NoCarry[ i ] );
|
||||
else fprintf( t, “,%02Xh”, NoCarry[ i ] );
|
||||
|
||||
fprintf( t, “,%02Xh”, Carry[ i ] );
|
||||
}
|
||||
}
|
||||
|
||||
fclose( t );
|
||||
}
|
||||
|
||||
|
||||
David’s approach is simplicity itself, although his implementation
|
||||
arguably is not. Consider any three sequential bytes in the buffer.
|
||||
Those three bytes define two potential places where a word might be
|
||||
counted, as shown in Figure 16.1. Given the separator/non-separator
|
||||
states of the three bytes, you can instantly determine whether to count
|
||||
a word or not; you count a word if and only if somewhere in the sequence
|
||||
there is a non-separator followed by a separator. Note that a maximum of
|
||||
one word can be counted per three-byte sequence.
|
||||
|
||||
The trick, then, is to identify the separator/not statuses of each set
|
||||
of three bytes and turn them into a 1 (count word) or 0 (don’t count
|
||||
word), as quickly as possible. Assuming that the separator/not status
|
||||
for the first byte is in the Carry flag, this is easily accomplished by
|
||||
a lookup in a 64K table, based on the Carry flag and the other two
|
||||
bytes, as shown in Figure 16.2. (Remember that we’re counting 7-bit
|
||||
ASCII here, so the high bit is ignored.) Thus, David is able to add the
|
||||
word/not status for each pair of bytes to the main word count simply by
|
||||
getting the two bytes, working in the carry status from the last byte,
|
||||
and using the resulting value to index into the 64K table, adding in the
|
||||
1 or 0 value found in that table. A sequence of **MOV/ADC/ADD** suffices
|
||||
to perform all word-counting tasks for a pair of bytes. Three
|
||||
instructions, no branches—pretty nearly perfect code.
|
||||
|
||||
\
|
||||
**Figure 16.1** *The two potential word count locations.*
|
||||
|
||||
One detail remains to be attended to: setting the Carry flag for next
|
||||
time if the last byte was a non-separator. David does this in a bizarre
|
||||
and incredibly effective way: He presets the high bit of the count, and
|
||||
sets the high bit in the lookup table for those entries looked up by
|
||||
non-separators. When a non-separator’s lookup entry is added to the
|
||||
count, it will produce a carry, as desired. The high bit of the count is
|
||||
masked off before being added to the total count, so David is
|
||||
essentially using different parts of the count variables for different
|
||||
purposes (counting, and setting the Carry flag).
|
||||
|
||||
\
|
||||
**Figure 16.2** *Looking up a word count status.*
|
||||
|
||||
There are a number of other interesting details in David’s code,
|
||||
including the unrolling of the loop 64 times, so that 256 bytes in a row
|
||||
are processed without a single branch. Unfortunately, I lack the space
|
||||
to discuss Listing 16.5 any further. Perhaps that’s not so unfortunate,
|
||||
after all; I’d hate to deny you the pleasure of discovering the wonders
|
||||
of this rather remarkable code yourself. I will say one more thing,
|
||||
though. The cycle count for David’s inner loop is 6.5 cycles per byte
|
||||
processed, and the actual measured time for his routine, overhead and
|
||||
all, is 7.9 cycles/byte. The original C code clocked in at around 100
|
||||
cycles/byte.
|
||||
|
||||
Enough said, I trust.
|
||||
|
||||
#### Enough Word Counting Already! {#Heading13}
|
||||
|
||||
Before I finish up this chapter, I’d like to mention that Terje
|
||||
Mathisen’s WC word-counting program, which I’ve mentioned previously and
|
||||
which is available, with source, on Bix, is in the ballpark with David’s
|
||||
code for performance. What’s more, Terje’s program handles 8-bit ASCII,
|
||||
counts lines as well as words, and supports user-definable separator
|
||||
sets. It’s wonderful code, well worth a look; it also happens to be a
|
||||
great word-counting utility. By the way, Terje builds his 64K table on
|
||||
the fly, at program initialization; this allows for customized tables,
|
||||
shrinks the size of the EXE, and, according to Terje’s calculations,
|
||||
takes less time than loading the table off disk as part of the EXE.
|
||||
|
||||
So, has David written the fastest possible word-counting code? Well,
|
||||
maybe—but I have a letter from Terry Holmes, of San Rafael, California,
|
||||
that calculates the theoretical maximum performance of native 386
|
||||
word-counting code at 5.5 cycles/byte, which would be significantly
|
||||
faster than David’s code. Terry, alas, didn’t bother to implement his
|
||||
design, but maybe I’ll take a shot at it someday. It’d be fun, for
|
||||
sure—but jeez, I’ve got *real* work to do!
|
||||
|
||||
------------------------ --------------------------------- --------------------
|
||||
[Previous](16-07.html) [Table of Contents](index.html) [Next](17-01.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
* * * * *
|
||||
|
||||
Graphics Programming Black Book © 2001 Michael Abrash
|
||||
Loading…
Reference in a new issue