252 lines
8.3 KiB
Markdown
252 lines
8.3 KiB
Markdown
Table 16.2 has two times for each entry listed: the first value is the
|
||
overall counting time, including time spent in the main program, disk
|
||
I/O, and everything else; the second value is the time actually spent
|
||
counting words, the time spent in **ScanBuffer** . The first value is
|
||
the time perceived by the user, but the second value best reflects the
|
||
quality of the optimization in each entry, since the rest of the overall
|
||
execution time is fixed.
|
||
|
||
* * * * *
|
||
|
||
Word-Counting Time
|
||
|
||
Name
|
||
|
||
Overall time
|
||
|
||
(ScanBuffer only)
|
||
|
||
* * * * *
|
||
|
||
David Stafford Listing 16.5
|
||
|
||
0.61 seconds
|
||
|
||
0.33 seconds
|
||
|
||
Dave Methvin
|
||
|
||
0.66
|
||
|
||
0.39
|
||
|
||
Mick Brown
|
||
|
||
0.70
|
||
|
||
0.41
|
||
|
||
Wendell Neubert
|
||
|
||
0.92
|
||
|
||
0.65
|
||
|
||
**For Comparison:**
|
||
|
||
Michael Abrash
|
||
|
||
1.73
|
||
|
||
1.44
|
||
|
||
assembly code
|
||
|
||
Listing 16.1
|
||
|
||
Michael Abrash
|
||
|
||
4.70
|
||
|
||
4.43
|
||
|
||
C code
|
||
|
||
Listing 16.4
|
||
|
||
**Note:** All times measured on a 20 MHz cached 386 DX.
|
||
|
||
Table 16.2 The top four word-counting entries.
|
||
|
||
* * * * *
|
||
|
||
**LISTING 16.5 QSCAN3.ASM**
|
||
|
||
; QSCAN3.ASM
|
||
; David Stafford
|
||
|
||
|
||
COMMENT $
|
||
|
||
How it works
|
||
——————
|
||
The idea is to go through the buffer fetching each letter-pair (words
|
||
rather than bytes). The carry flag indicates whether we are
|
||
currently in a (text) word or not. The letter-pair fetched from the
|
||
buffer is converted to a 16-bit address by shifting it left one bit
|
||
(losing the high bit of the second character) and putting the carry
|
||
flag in the low bit. The high bit of the count register is set to
|
||
1. Then the count register is added to the byte found at the given
|
||
address in a large (64K, naturally) table. The byte at the given
|
||
address will contain a 1 in the high bit if the last character of the
|
||
letter-pair is a word-letter (alphanumeric or apostrophe). This will
|
||
set the carry flag since the high bit of the count register is also a
|
||
1. The low bit of the byte found at the given address will be one if
|
||
the second character of the previous letter-pair was a word-letter
|
||
and the first character of this letter-pair is not a word-letter. It
|
||
will also be 1 if the first character of this letter-pair is a
|
||
word-letter but the second character is not. This process is
|
||
repeated. Finally, the carry flag is saved to indicate the final
|
||
in-a-word/not-in-a-word status. The count register is masked to
|
||
remove the high bit and the count of words remains in the count
|
||
register.
|
||
Sound complicated? You're right! But it's fast!
|
||
|
||
The beauty of this method is that no jumps are required, the
|
||
operations are fast, it requires only one table and the process can
|
||
be repeated (unrolled) many times. QSCAN3 can read 256 bytes without
|
||
jumping.
|
||
|
||
COMMEND $
|
||
.model small
|
||
.code
|
||
|
||
Test1 macro x,y ;9 or 10 bytes
|
||
Addr&x: mov di,[bp+y] ;3 or 4 bytes
|
||
adc di,di
|
||
or ax,si
|
||
add al,[di]
|
||
endm
|
||
|
||
Test2 macro x,y ;7 or 8 bytes
|
||
Addr&x: mov di,[bp+y] ;3 or 4 bytes
|
||
adc di,di
|
||
add ah,[di]
|
||
endm
|
||
|
||
Scan = 128 ;scan 256 bytes at a time
|
||
Buffer = 4 ;parms
|
||
BufferLength = 6
|
||
CharFlag = 8
|
||
WordCount = 10
|
||
|
||
public _ScanBuffer
|
||
_ScanBuffer proc near
|
||
push bp
|
||
mov bp,sp
|
||
push si
|
||
push di
|
||
|
||
xor cx,cx
|
||
mov si,[bp+Buffer] ;si = text buffer
|
||
mov ax,[bp+BufferLength] ;dx = length in bytes
|
||
shr ax,1 ;dx = length in words
|
||
jnz NormalBuf
|
||
OneByteBuf:
|
||
mov ax,seg WordTable
|
||
mov es,ax
|
||
|
||
mov di,[bp+CharFlag]
|
||
mov bh,[di] ;bh = old CharFlag
|
||
mov bl,[si] ;bl = character
|
||
add bh,‘A'-1 ;make bh into character
|
||
add bx,bx ;prepare to index
|
||
mov al,es:[bx]
|
||
cbw ;get hi bit in ah (then bh)
|
||
shr al,1 ;get low bit
|
||
adc cx,cx ;cx = 0 or 1
|
||
xchg ax,bx
|
||
jmp CleanUp
|
||
NormalBuf:
|
||
push bp ;(1)
|
||
pushf ;(2)
|
||
|
||
cwd ;dx = 0
|
||
mov cl,Scan
|
||
div cx
|
||
or dx,dx ;remainder?
|
||
jz StartAtTheTop ;nope, do the whole banana
|
||
sub cx,dx
|
||
sub si,cx ;adjust buf pointer
|
||
sub si,cx
|
||
inc ax ;adjust for partial read
|
||
|
||
StartAtTheTop: mov bx,dx ;get index for start...
|
||
shl bx,1
|
||
mov di,LoopEntry[bx] ;...address in di
|
||
xchg dx,ax ;dx is the loop counter
|
||
xor cx,cx ;total word count
|
||
mov bx,[bp+CharFlag]
|
||
mov bl,[bx] ;bl = old CharFlag
|
||
mov bp,seg WordTable
|
||
mov ds,bp
|
||
mov bp,si ;scan buffer with bp
|
||
mov si,8080h ;hi bits
|
||
mov ax,si ;init local word counter
|
||
shr bl,1 ;carry = old CharFlag
|
||
jmp di
|
||
|
||
align 2
|
||
Top: add bx,bx ;restore carry
|
||
n = 0
|
||
rept Scan/2
|
||
Test1 %n,%n*2
|
||
Test2 %n+1,%n*2+2
|
||
n = n+2
|
||
endm
|
||
EndCount:
|
||
sbb bx,bx ;save carry
|
||
if Scan ge 128 ;because al+ah may equal 128!
|
||
or ax,si
|
||
add al,ah
|
||
mov ah,0
|
||
else
|
||
add al,ah
|
||
and ax,7fh ;mask
|
||
endif
|
||
add cx,ax ;update word count
|
||
mov ax,si
|
||
add bp,Scan*2
|
||
dec dx ;any left?
|
||
jng Quit
|
||
jmp Top
|
||
|
||
Quit: popf ;(2) even or odd buffer?
|
||
jnc ItsEven
|
||
clc
|
||
Test1 Odd,-1
|
||
sbb bx,bx ;save carry
|
||
shr ax,1
|
||
adc cx,0
|
||
ItsEven:
|
||
push ss ;restore ds
|
||
pop ds
|
||
pop bp ;(1)
|
||
CleanUp:
|
||
mov si,[bp+WordCount]
|
||
add [si],cx
|
||
adc word ptr [si+2],0
|
||
and bh,1 ;save only the carry flag
|
||
mov si,[bp+CharFlag]
|
||
mov [si],bh
|
||
pop di
|
||
pop si
|
||
pop bp
|
||
ret
|
||
_ScanBuffer endp
|
||
|
||
.data
|
||
Address macro X
|
||
dw Addr&X
|
||
endm
|
||
|
||
LoopEntry label word
|
||
n = Scan
|
||
REPT Scan
|
||
Address %n MOD Scan
|
||
n = n - 1
|
||
ENDM
|
||
|
||
.fardata WordTable
|
||
include qscan3.inc ;built by MAKETAB
|
||
end
|