Initial conversion to markdown
This commit is contained in:
parent
a96a114403
commit
5e10794a24
362 changed files with 67353 additions and 0 deletions
169
16-03.md
Normal file
169
16-03.md
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
------------------------ --------------------------------- --------------------
|
||||
[Previous](16-02.html) [Table of Contents](index.html) [Next](16-04.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
**LISTING 16.4 L16-4.ASM**
|
||||
|
||||
; Assembly subroutine for Listing 16.2. Scans through Buffer, of
|
||||
; length BufferLength, counting words and updating WordCount as
|
||||
; appropriate, using a lookup table-based approach. 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
|
||||
.data
|
||||
; Table of char/not statuses for byte values 0-255 (128-255 are
|
||||
; duplicates of 0-127 to effectively mask off bit 7, which some
|
||||
; word processors set as an internal flag).
|
||||
CharStatusTable label byte
|
||||
REPT 2
|
||||
db 39 dup(0)
|
||||
db 1 ;apostrophe
|
||||
db 8 dup(0)
|
||||
db 10 dup(1) ;0-9
|
||||
db 7 dup(0)
|
||||
db 26 dup(1) ;A-Z
|
||||
db 6 dup(0)
|
||||
db 26 dup(1) ;a-z
|
||||
db 5 dup(0)
|
||||
ENDM
|
||||
|
||||
.code
|
||||
public _ScanBuffer
|
||||
_ScanBuffer proc near
|
||||
push bp ;preserve caller’s stack frame
|
||||
mov bp,sp ;set up local stack frame
|
||||
push si ;preserve caller’s register vars
|
||||
push di
|
||||
|
||||
mov si,[bp+Buffer] ;point to buffer to scan
|
||||
mov bx,[bp+WordCount]
|
||||
mov di,[bx] ;get current 32-bit word count
|
||||
mov dx,[bx+2]
|
||||
mov bx,[bp+CharFlag]
|
||||
mov al,[bx] ;get current CharFlag
|
||||
mov cx,[bp+BufferLength] ;get # of bytes to scan
|
||||
mov bx,offset CharStatusTable
|
||||
ScanLoop:
|
||||
and al,al ;ZF=0 if last byte was a char,
|
||||
; ZF=1 if not
|
||||
lodsb ;get the next byte
|
||||
;***doesn’t change flags***
|
||||
xlat ;look up its char/not status
|
||||
;***doesn’t change flags***
|
||||
jz ScanLoopBottom ;don’t count a word if last byte was
|
||||
; not a character
|
||||
and al,al ;last byte was a character; is the
|
||||
; current byte a character?
|
||||
jz CountWord ;no, so count a word
|
||||
ScanLoopBottom:
|
||||
dec cx ;count down buffer length
|
||||
jnz ScanLoop
|
||||
Done:
|
||||
mov si,[bp+CharFlag]
|
||||
mov [si],al ;set new CharFlag
|
||||
mov bx,[bp+WordCount]
|
||||
mov [bx],di ;set new word count
|
||||
mov [bx+2],dx
|
||||
|
||||
pop di ;restore caller’s register vars
|
||||
pop si
|
||||
pop bp ;restore caller’s stack frame
|
||||
ret
|
||||
|
||||
align 2
|
||||
CountWord:
|
||||
add di,1 ;increment the word count
|
||||
adc dx,0
|
||||
dec cx ;count down buffer length
|
||||
jnz ScanLoop
|
||||
jmp Done
|
||||
_ScanBuffer endp
|
||||
end
|
||||
|
||||
|
||||
Listing 16.4 features several interesting tricks. First, it uses
|
||||
**LODSB** and **XLAT** in succession, a very neat way to get a
|
||||
pointed-to byte, advance the pointer, and look up the value indexed by
|
||||
the byte in a table, all with just two instruction bytes.
|
||||
(Interestingly, Listing 16.4 would probably run quite a bit better still
|
||||
on an 8088, where **LODSB** and **XLAT** have a greater advantage over
|
||||
conventional instructions. On the 486 and Pentium, however, **LODSB**
|
||||
and **XLAT** lose much of their appeal, and should be replaced with
|
||||
**MOV** instructions.) Better yet, **LODSB** and **XLAT** don’t alter
|
||||
the flags, so the Zero flag status set before **LODSB** is still around
|
||||
to be tested after **XLAT** .
|
||||
|
||||
Finally, if you look closely, you will see that Listing 16.4 jumps out
|
||||
of the loop to increment the word count in the case where a word is
|
||||
actually found, with a duplicate of the loop-bottom code placed after
|
||||
the code that increments the word count, to avoid an extra branch back
|
||||
into the loop; this replaces the more intuitive approach of jumping
|
||||
around the incrementing code to the loop bottom when a word isn’t found.
|
||||
Although this incurs a branch every time a word is found, a word is
|
||||
typically found only once every 5 or 6 bytes; on average, then, a branch
|
||||
is saved about two-thirds of the time. This is an excellent example of
|
||||
how understanding the nature of the data you’re processing allows you to
|
||||
optimize in ways the compiler can’t. *Know your data!*
|
||||
|
||||
So, gosh, Listing 16.4 is the best word-counting code in the universe,
|
||||
right? Not hardly. If there’s one thing my years of toil in this vale of
|
||||
silicon have taught me, it’s that there’s never a lack of potential for
|
||||
further optimization. *Never!* Off the top of my head, I can think of at
|
||||
least three ways to speed up Listing 16.4; and, since Turbo Profiler
|
||||
reports that even in Listing 16.4, 88 percent of the time is spent
|
||||
scanning the buffer (as opposed to reading the file), there’s potential
|
||||
for those further optimizations to improve performance significantly.
|
||||
(However, it is true that when access is performed to a hard rather than
|
||||
RAM disk, disk access jumps to about half of overall execution time.)
|
||||
One possible optimization is unrolling the loop, although that is truly
|
||||
a last resort because it tends to make further changes extremely
|
||||
difficult.
|
||||
|
||||
------------------- -----------------------------------------------------------
|
||||
 *Exhaust all other optimizations before unrolling loops.*
|
||||
------------------- -----------------------------------------------------------
|
||||
|
||||
### Challenges and Hazards {#Heading5}
|
||||
|
||||
The challenge I put to the readers of *PC TECHNIQUES* was to write a
|
||||
faster module to replace Listing 16.4. The author of the code that
|
||||
counted the words in my secret test file fastest on my 20 MHz cached 386
|
||||
would be the winner and receive Numerous Valuable Prizes.
|
||||
|
||||
No listings were to be longer than 200 lines. No complete programs were
|
||||
to be accepted; submissions had to be plug-compatible with Listing 16.4.
|
||||
(This was to encourage people not to waste time optimizing outside the
|
||||
inner loop.) Finally, the code had to produce the same results as
|
||||
Listing 16.4; I didn’t want to see functions that approximated the word
|
||||
count by dividing the number of characters by six instead of counting
|
||||
actual words!
|
||||
|
||||
So how did the entrants in this particular challenge stack up? More than
|
||||
one claimed a speed-up over my assembly word-counting code of more than
|
||||
three times. On top of the three-times speedup over the original C code
|
||||
that I had already realized, we’re almost up to an order of magnitude
|
||||
faster. You are, of course, entitled to your own opinion, but *I*
|
||||
consider an order of magnitude to be significant.
|
||||
|
||||
------------------------ --------------------------------- --------------------
|
||||
[Previous](16-02.html) [Table of Contents](index.html) [Next](16-04.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
* * * * *
|
||||
|
||||
Graphics Programming Black Book © 2001 Michael Abrash
|
||||
Loading…
Reference in a new issue