Normal quotes
This commit is contained in:
parent
5e10794a24
commit
18011c595a
352 changed files with 6040 additions and 6040 deletions
38
09-02.md
38
09-02.md
|
|
@ -2,15 +2,15 @@
|
|||
[Previous](09-01.html) [Table of Contents](index.html) [Next](09-03.html)
|
||||
------------------------ --------------------------------- --------------------
|
||||
|
||||
However, it generally is. Sure, if the length is odd, John’s approach
|
||||
However, it generally is. Sure, if the length is odd, John's approach
|
||||
incurs a penalty approximately equal to the **REP** startup time for
|
||||
**MOVSB**. However, if the length is even, John’s approach doesn’t
|
||||
**MOVSB**. However, if the length is even, John's approach doesn't
|
||||
branch, saving cycles and not emptying the prefetch queue. If copy
|
||||
lengths are evenly distributed between even and odd, John’s approach is
|
||||
lengths are evenly distributed between even and odd, John's approach is
|
||||
faster in most x86 systems. (Not on the 486, though.)
|
||||
|
||||
John also points out that on the 386, multiple **LEA**s can be combined
|
||||
to perform multiplications that can’t be handled by a single **LEA**,
|
||||
to perform multiplications that can't be handled by a single **LEA**,
|
||||
much as multiple shifts and adds can be used for multiplication, only
|
||||
faster. **LEA** can be used to multiply in a single instruction on the
|
||||
386, but only by the values 2, 3, 4, 5, 8, and 9; several **LEA**s
|
||||
|
|
@ -34,7 +34,7 @@ Using **LEA** on the 386, the above could be reduced to
|
|||
LEA EAX,[EAX*8] ;*16
|
||||
LEA EAX,[EAX+EAX*4] ;*80
|
||||
|
||||
which still isn’t as fast as using a lookup table like
|
||||
which still isn't as fast as using a lookup table like
|
||||
|
||||
MOV EAX,MultiplesOf80Table[EAX*4]
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ and one that allows for considerable optimization. One way to speed up
|
|||
multiplication is to use shift and add, **LEA**, or a lookup table to
|
||||
hard-code a multiplication operation for a fixed multiplier, as shown
|
||||
above. Another is to take advantage of the early-out feature of the 386
|
||||
(and the 486, but in the interests of brevity I’ll just say “386” from
|
||||
(and the 486, but in the interests of brevity I'll just say "386" from
|
||||
now on) by arranging your operands so that the multiplier (always the
|
||||
rightmost operand following **MUL** or **IMUL**) is no larger than the
|
||||
other operand.
|
||||
|
|
@ -64,7 +64,7 @@ other operand.
|
|||
 *Why? Because the 386 processes one multiplier bit per cycle and immediately ends a multiplication when all significant bits of the multiplier have been processed, so fewer cycles are required to multiply a large multiplicand times a small multiplier than a small multiplicand times a large multiplier, by a factor of about 1 cycle for each significant multiplier bit eliminated.*
|
||||
------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
(There’s a minimum execution time on this trick; below 3 significant
|
||||
(There's a minimum execution time on this trick; below 3 significant
|
||||
multiplier bits, no additional cycles are saved.) For example,
|
||||
multiplication of 32,767 times 1 is 12 cycles faster than multiplication
|
||||
of 1 times 32,727.
|
||||
|
|
@ -82,7 +82,7 @@ This highlights another interesting point: **MUL** and **IMUL** on the
|
|||
generally still faster, are worthwhile only in truly time-critical code.
|
||||
|
||||
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
 *On 386SXs and uncached 386s, where code size can significantly affect performance due to instruction prefetching, the compact **MUL** and **IMUL** instructions can approach and in some cases even outperform the “optimized” alternatives.*
|
||||
 *On 386SXs and uncached 386s, where code size can significantly affect performance due to instruction prefetching, the compact **MUL** and **IMUL** instructions can approach and in some cases even outperform the "optimized" alternatives.*
|
||||
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
All in all, **MUL** and **IMUL** are reasonable performers on the 386,
|
||||
|
|
@ -90,10 +90,10 @@ no longer to be avoided in most cases—and you can help that along by
|
|||
arranging your code to make the smaller operand the multiplier whenever
|
||||
you know which operand is smaller.
|
||||
|
||||
That doesn’t mean that your code should test and swap operands to make
|
||||
sure the smaller one is the multiplier; that rarely pays off. I’m
|
||||
speaking more of the case where you’re scaling an array up by a value
|
||||
that’s always in the range of, say, 2 to 10; because the scale value
|
||||
That doesn't mean that your code should test and swap operands to make
|
||||
sure the smaller one is the multiplier; that rarely pays off. I'm
|
||||
speaking more of the case where you're scaling an array up by a value
|
||||
that's always in the range of, say, 2 to 10; because the scale value
|
||||
will always be small and the array elements may have any value, the
|
||||
scale value is the logical choice for the multiplier.
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ quick refresher, I described searching a buffer for a text string as
|
|||
follows: Scan for the first byte of the text string with **REPNZ
|
||||
SCASB**, then use **REPZ CMPS** to check for a full match whenever
|
||||
**REPNZ SCASB** finds a match for the first character, as shown in
|
||||
Figure 9.1. The principle is that most buffer characters won’t match the
|
||||
Figure 9.1. The principle is that most buffer characters won't match the
|
||||
first character of any given string, so **REPNZ SCASB**, by far the
|
||||
fastest way to search on the PC, can be used to eliminate most potential
|
||||
matches; each remaining potential match can then be checked in its
|
||||
|
|
@ -114,7 +114,7 @@ entirety with **REPZ CMPS**.
|
|||
\
|
||||
**Figure 9.1** *Simple searching method for locating a text string.*
|
||||
|
||||
Rob’s revelation, which he credits without explanation to Edgar Allen
|
||||
Rob's revelation, which he credits without explanation to Edgar Allen
|
||||
Poe (search nevermore?), was that by far the slowest part of the whole
|
||||
deal is handling **REPNZ SCASB** matches, which require checking the
|
||||
remainder of the string with **REPZ CMPS** and restarting **REPNZ
|
||||
|
|
@ -124,11 +124,11 @@ SCASB** if no match is found.
|
|||
 *Rob points out that the number of **REPNZ SCASB** matches can easily be reduced simply by scanning for the character in the searched-for string that appears least often in the buffer being searched.*
|
||||
------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
Imagine, if you will, that you’re searching for the string “EQUAL.” By
|
||||
my approach, you’d use **REPNZ SCASB** to scan for each occurrence of
|
||||
“E,” which crops up quite often in normal text. Rob points out that it
|
||||
would make more sense to scan for “Q,” then back up one character and
|
||||
check the whole string when a “Q” is found, as shown in Figure 9.2. “Q”
|
||||
Imagine, if you will, that you're searching for the string "EQUAL." By
|
||||
my approach, you'd use **REPNZ SCASB** to scan for each occurrence of
|
||||
"E," which crops up quite often in normal text. Rob points out that it
|
||||
would make more sense to scan for "Q," then back up one character and
|
||||
check the whole string when a "Q" is found, as shown in Figure 9.2. "Q"
|
||||
is likely to occur much less often, resulting in many fewer whole-string
|
||||
checks and much faster processing.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue