
|
 |
|
Michael Abrash's Graphics Programming Black Book, Special Edition
by Michael Abrash
The Coriolis Group
ISBN: 1576101746 Pub Date: 07/01/97
|
Listing 16.2 is Listing 16.1 modified to call a function that scans each block for words, and Listing 16.3 contains an assembly function that counts words. Used together, Listings 16.2 and 16.3 are just about twice as fast as Listing 16.1, a good return for a little assembly language. Listing 16.3 is a pretty straightforward translation from C to assembly; the new code makes good use of registers, but the key codedetermining whether each byte is a character or notis still done with the same multiple-sequential-tests approach used by the code that the C compiler generates.
LISTING 16.2 L16-2.C
/* Word-counting program incorporating assembly language. Tested
with Borland C++ in C compilation mode & the small model. */
#include <stdio.h>
#include <fcntl.h>
#include <sys\stat.h>
#include <stdlib.h>
#include <io.h>
#define BUFFER_SIZE 0x8000 /* largest chunk of file worked
with at any one time */
int main(int, char **);
void ScanBuffer(char *, unsigned int, char *, unsigned long *);
int main(int argc, char **argv) {
int Handle;
unsigned int BlockSize;
long FileSize;
unsigned long WordCount = 0;
char *Buffer, CharFlag = 0;
if (argc != 2) {
printf(usage: wc <filename>\n);
exit(1);
}
if ((Buffer = malloc(BUFFER_SIZE)) == NULL) {
printf(Cant allocate adequate memory\n);
exit(1);
}
if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) {
printf(Cant open file %s\n, argv[1]);
exit(1);
}
if ((FileSize = filelength(Handle)) == -1) {
printf(Error sizing file %s\n, argv[1]);
exit(1);
}
CharFlag = 0;
while (FileSize > 0) {
FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE));
if (read(Handle, Buffer, BlockSize) == -1) {
printf(Error reading file %s\n, argv[1]);
exit(1);
}
ScanBuffer(Buffer, BlockSize, &CharFlag, &WordCount);
}
/* Catch the last word, if any */
if (CharFlag) {
WordCount++;
}
printf(\nTotal words in file: %lu\n, WordCount);
return(0);
}
LISTING 16.3 L16-3.ASM
; Assembly subroutine for Listing 16.2. Scans through Buffer, of
; length BufferLength, counting words and updating WordCount as
; appropriate. BufferLength must be > 0. *CharFlag and *WordCount
; should equal 0 on the first call. Tested with TASM.
; C near-callable as:
; void ScanBuffer(char *Buffer, unsigned int BufferLength,
; char *CharFlag, unsigned long *WordCount);
parms struc
dw 2 dup(?) ;pushed return address & BP
Buffer dw ? ;buffer to scan
BufferLength dw ? ;length of buffer to scan
CharFlag dw ? ;pointer to flag for state of last
; char processed on entry (0 on
; initial call). Updated on exit
WordCount dw ? ;pointer to 32-bit count of words
; found (0 on initial call)
parms ends
.model small
.code
public _ScanBuffer
_ScanBuffer proc near
push bp ;preserve callers stack frame
mov bp,sp ;set up local stack frame
push si ;preserve callers register vars
push di
mov si,[bp+Buffer] ;point to buffer to scan
mov bx,[bp+WordCount]
mov cx,[bx] ;get current 32-bit word count
mov dx,[bx+2]
mov bx,[bp+CharFlag]
mov bl,[bx] ;get current CharFlag
mov di,[bp+BufferLength];get # of bytes to scan
ScanLoop:
mov bh,bl ;PredCharFlag = CharFlag;
lodsb ;Ch = *BufferPtr++ & 0x7F;
and al,7fh ;strip high bit for word processors
; that set it as an internal flag
mov bl,1 ;assume this is a char; CharFlag = 1;
cmp al,a ;it is a char if between a and z
jb CheckAZ
cmp al,z
jna IsAChar
CheckAZ:
cmp al,A ;it is a char if between A and Z
jb Check09
cmp al,Z
jna IsAChar
Check09:
cmp al,0 ;it is a char if between 0 and 9
jb CheckApostrophe
cmp al,9
jna IsAChar
CheckApostrophe:
cmp al,27h ;it is a char if an apostrophe
jz IsAChar
sub bl,bl ;not a char; CharFlag = 0;
and bh,bh
jz ScanLoopBottom ;if ((!CharFlag) && PredCharFlag) {
add cx,1 ; (WordCount)++;
adc dx,0 ;}
IsAChar:
ScanLoopBottom:
dec di ;} while (BufferLength);
jnz ScanLoop
mov si,[bp+CharFlag]
mov [si],bl ;set new CharFlag
mov bx,[bp+WordCount]
mov [bx],cx ;set new word count
mov [bx+2],dx
pop di ;restore callers register vars
pop si
pop bp ;restore callers stack frame
ret
_ScanBuffer endp
end
Which Way to Go from Here?
We could rearrange the tests in light of the nature of the data being scanned; for example, we could perform the tests more efficiently by taking advantage of the knowledge that if a byte is less than 0, its either an apostrophe or not a character at all. However, that sort of fine-tuning is typically good for speedups of only 10 to 20 percent, and Ive intentionally refrained from implementing this in Listing 16.3 to avoid pointing you down the wrong path; what we need is a different tack altogether. Ponder this. What we really want to know is nothing more than whether a byte is a character, not what sort of character it is. For each byte value, we want a yes/no status, and nothing elseand that description practically begs for a lookup table. Listing 16.4 uses a lookup table approach to boost performance another 50 percent, to three times the performance of the original C code. On a 20 MHz 386, this represents a change from 4.6 to 1.6 seconds, which could be significantwho likes to wait? On an 8088, the improvement in word-counting a large file could easily be 10 or 20 seconds, which is definitely significant.
|