Use backtick code blocks for syntax highlighting

This commit is contained in:
James Gregory 2013-12-31 11:38:48 +11:00
commit f658bfbb40
203 changed files with 26546 additions and 25801 deletions

View file

@ -82,6 +82,7 @@ It's *slow*.
**LISTING 1.1 L1-1.C** **LISTING 1.1 L1-1.C**
```c
/* /*
* Program to calculate the 16-bit checksum of all bytes in the * Program to calculate the 16-bit checksum of all bytes in the
* specified file. Obtains the bytes one at a time via read(), * specified file. Obtains the bytes one at a time via read(),
@ -122,6 +123,7 @@ It's *slow*.
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```
Table 1.1 shows the time taken for Listing 1.1 to generate a checksum of Table 1.1 shows the time taken for Listing 1.1 to generate a checksum of
the WordPerfect version 4.2 thesaurus file, TH.WP (362,293 bytes in the WordPerfect version 4.2 thesaurus file, TH.WP (362,293 bytes in

View file

@ -30,6 +30,7 @@ disk caching turned off.
**LISTING 1.2 L1-2.C** **LISTING 1.2 L1-2.C**
```c
/* /*
* Program to calculate the 16-bit checksum of the stream of bytes * Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Obtains the bytes one at a time in * from the specified file. Obtains the bytes one at a time in
@ -62,9 +63,11 @@ disk caching turned off.
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```
**LISTING 1.3 L1-3.ASM** **LISTING 1.3 L1-3.ASM**
```nasm
; Assembler subroutine to perform a 16-bit checksum on the file ; Assembler subroutine to perform a 16-bit checksum on the file
; opened on the passed-in handle. Stores the result in the ; opened on the passed-in handle. Stores the result in the
; passed-in checksum variable. Returns 1 for success, 0 for error. ; passed-in checksum variable. Returns 1 for success, 0 for error.
@ -129,6 +132,7 @@ disk caching turned off.
ret ret
_ChecksumFileendp _ChecksumFileendp
end end
```
The lesson is clear: Optimization makes code faster, but without proper The lesson is clear: Optimization makes code faster, but without proper
design, optimization just creates fast slow code. design, optimization just creates fast slow code.

View file

@ -20,6 +20,7 @@ libraries do their work. In other words, *know the territory*!
**LISTING 1.4 L1-4.C** **LISTING 1.4 L1-4.C**
```c
/* /*
* Program to calculate the 16-bit checksum of the stream of bytes * Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Obtains the bytes one at a time via * from the specified file. Obtains the bytes one at a time via
@ -53,6 +54,7 @@ libraries do their work. In other words, *know the territory*!
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```
#### Know When It Matters {#Heading10} #### Know When It Matters {#Heading10}

View file

@ -30,6 +30,7 @@ uses no assembly at all.
**LISTING 1.5 L1-5.C** **LISTING 1.5 L1-5.C**
```c
/* /*
* Program to calculate the 16-bit checksum of the stream of bytes * Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Buffers the bytes internally, rather * from the specified file. Buffers the bytes internally, rather
@ -86,6 +87,7 @@ uses no assembly at all.
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```
That brings us to the fourth reason: avoiding an internal-buffered That brings us to the fourth reason: avoiding an internal-buffered
implementation like Listing 1.5 because of the difficulty of coding such implementation like Listing 1.5 because of the difficulty of coding such
@ -122,6 +124,7 @@ the design has been maxed out.
**LISTING 1.6 L1-6.C** **LISTING 1.6 L1-6.C**
```c
/* /*
* Program to calculate the 16-bit checksum of the stream of bytes * Program to calculate the 16-bit checksum of the stream of bytes
* from the specified file. Buffers the bytes internally, rather * from the specified file. Buffers the bytes internally, rather
@ -175,3 +178,4 @@ the design has been maxed out.
printf("The checksum is: %u\n", Checksum); printf("The checksum is: %u\n", Checksum);
exit(0); exit(0);
} }
```

View file

@ -12,6 +12,7 @@ pages: 018-019
**LISTING 1.7 L1-7.ASM** **LISTING 1.7 L1-7.ASM**
```nasm
; Assembler subroutine to perform a 16-bit checksum on a block of ; Assembler subroutine to perform a 16-bit checksum on a block of
; bytes 1 to 64K in size. Adds checksum for block into passed-in ; bytes 1 to 64K in size. Adds checksum for block into passed-in
; checksum. ; checksum.
@ -61,6 +62,7 @@ pages: 018-019
ret ret
_ChecksumChunkendp _ChecksumChunkendp
end end
```
Note that in Table 1.1, optimization makes little difference except in Note that in Table 1.1, optimization makes little difference except in
the case of Listing 1.5, where the design has been refined considerably. the case of Listing 1.5, where the design has been refined considerably.

View file

@ -45,6 +45,7 @@ bytes.) I examined the subroutine line by line, saving a cycle here and
a cycle there, until the code truly seemed to be optimized. When I was a cycle there, until the code truly seemed to be optimized. When I was
done, the key part of the code looked something like this: done, the key part of the code looked something like this:
```nasm
LoopTop: LoopTop:
lodsb ;get the next byte to extract a bit from lodsb ;get the next byte to extract a bit from
and al,ah ;isolate the bit we want and al,ah ;isolate the bit we want
@ -53,6 +54,7 @@ done, the key part of the code looked something like this:
dec cx ;the next bit goes 1 place to the right dec cx ;the next bit goes 1 place to the right
dec dx ;count down the number of bits dec dx ;count down the number of bits
jnz LoopTop ;process the next bit, if any jnz LoopTop ;process the next bit, if any
```
Now, it's hard to write code that's much faster than seven instructions, Now, it's hard to write code that's much faster than seven instructions,
only one of which accesses memory, and most programmers would have only one of which accesses memory, and most programmers would have
@ -68,6 +70,7 @@ total of four separate time-consuming multibit rotations!
I changed the code to the following: I changed the code to the following:
```nasm
LoopTop: LoopTop:
lodsb ;get the next byte to extract a bit from lodsb ;get the next byte to extract a bit from
and al,ah ;isolate the bit we want and al,ah ;isolate the bit we want
@ -77,6 +80,7 @@ I changed the code to the following:
jnz LoopTop ;process the next bit, if any jnz LoopTop ;process the next bit, if any
rol bl,cl ;rotate all four bits into their final rol bl,cl ;rotate all four bits into their final
; positions at the same time ; positions at the same time
```
This moved the costly multibit rotation out of the loop so that it was This moved the costly multibit rotation out of the loop so that it was
performed just once, rather than four times. While the code may not look performed just once, rather than four times. While the code may not look

View file

@ -12,6 +12,7 @@ pages: 035-042
**LISTING 3.1 PZTIMER.ASM** **LISTING 3.1 PZTIMER.ASM**
```nasm
; The precision Zen timer (PZTIMER.ASM) ; The precision Zen timer (PZTIMER.ASM)
; ;
; Uses the 8253 timer to time the performance of code that takes ; Uses the 8253 timer to time the performance of code that takes
@ -451,3 +452,4 @@ pages: 035-042
Code ends Code ends
end end
```

View file

@ -82,6 +82,7 @@ and should contain calls to **ZTimerOn** and **ZTimerOff** .
**LISTING 3.2 PZTEST.ASM** **LISTING 3.2 PZTEST.ASM**
```nasm
; Program to measure performance of code that takes less than ; Program to measure performance of code that takes less than
; 54 ms to execute. (PZTEST.ASM) ; 54 ms to execute. (PZTEST.ASM)
; ;
@ -119,6 +120,7 @@ and should contain calls to **ZTimerOn** and **ZTimerOff** .
Start endp Start endp
Code ends Code ends
end Start end Start
```
Listing 3.3 shows some sample code to be timed. This listing measures Listing 3.3 shows some sample code to be timed. This listing measures
the time required to execute 1,000 loads of AL from the memory variable the time required to execute 1,000 loads of AL from the memory variable
@ -130,6 +132,7 @@ after the code in Listing 3.3 has been run.
**LISTING 3.3 LST3-3.ASM** **LISTING 3.3 LST3-3.ASM**
```nasm
; Test file; ; Test file;
; Measures the performance of 1,000 loads of AL from ; Measures the performance of 1,000 loads of AL from
; memory. (Use by renaming to TESTCODE, which is ; memory. (Use by renaming to TESTCODE, which is
@ -154,6 +157,7 @@ after the code in Listing 3.3 has been run.
; Stop timing. ; Stop timing.
; ;
call ZTimerOff call ZTimerOff
```
It's worth noting that Listing 3.3 begins by jumping around the memory It's worth noting that Listing 3.3 begins by jumping around the memory
variable **MemVar**. This approach lets us avoid reproducing Listing 3.2 variable **MemVar**. This approach lets us avoid reproducing Listing 3.2

View file

@ -25,6 +25,7 @@ and "link" with "tlink" in Listing 3.4. The same is true of Listing
**LISTING 3.4 PZTIME.BAT** **LISTING 3.4 PZTIME.BAT**
```bat
echo off echo off
rem rem
rem *** Listing 3.4 *** rem *** Listing 3.4 ***
@ -86,11 +87,14 @@ and "link" with "tlink" in Listing 3.4. The same is true of Listing
echo * An error occurred while building the precision Zen timer. * echo * An error occurred while building the precision Zen timer. *
echo *************************************************************** echo ***************************************************************
:end :end
```
Assuming that Listing 3.3 is named LST3-3.ASM and Listing 3.4 is named Assuming that Listing 3.3 is named LST3-3.ASM and Listing 3.4 is named
PZTIME.BAT, the code in Listing 3.3 would be timed with the command: PZTIME.BAT, the code in Listing 3.3 would be timed with the command:
```sh
pztime LST3-3.ASM pztime LST3-3.ASM
```
which performs all assembly and linking, and reports the execution time which performs all assembly and linking, and reports the execution time
of the code in Listing 3.3. of the code in Listing 3.3.
@ -116,7 +120,9 @@ In order to perform any of the timing tests in this book, enter Listing
and enter Listing 3.4 and name it PZTIME.BAT. Then simply enter the and enter Listing 3.4 and name it PZTIME.BAT. Then simply enter the
listing you wish to run into the file *filename* and enter the command: listing you wish to run into the file *filename* and enter the command:
```sh
pztime <filename> pztime <filename>
```
In fact, that's exactly how I timed each of the listings in this book. In fact, that's exactly how I timed each of the listings in this book.
Code fragments you write yourself can be timed in just the same way. If Code fragments you write yourself can be timed in just the same way. If

View file

@ -78,6 +78,7 @@ computers.
**LISTING 3.5 LZTIMER.ASM** **LISTING 3.5 LZTIMER.ASM**
```nasm
; ;
; The long-period Zen timer. (LZTIMER.ASM) ; The long-period Zen timer. (LZTIMER.ASM)
; Uses the 8253 timer and the BIOS time-of-day count to time the ; Uses the 8253 timer and the BIOS time-of-day count to time the
@ -712,3 +713,4 @@ computers.
Code ends Code ends
end end
```

View file

@ -70,6 +70,7 @@ timing.
**LISTING 3.6 LZTEST.ASM** **LISTING 3.6 LZTEST.ASM**
```nasm
; Program to measure performance of code that takes longer than ; Program to measure performance of code that takes longer than
; 54 ms to execute. (LZTEST.ASM) ; 54 ms to execute. (LZTEST.ASM)
; ;
@ -128,6 +129,7 @@ timing.
Start endp Start endp
Code ends Code ends
end Start end Start
```
As with the precision Zen timer, the program in Listing 3.6 is used by As with the precision Zen timer, the program in Listing 3.6 is used by
naming the file containing the code to be timed TESTCODE, then naming the file containing the code to be timed TESTCODE, then

View file

@ -12,6 +12,7 @@ pages: 067-070
**LISTING 3.7 LZTIME.BAT** **LISTING 3.7 LZTIME.BAT**
```bat
echo off echo off
rem rem
rem *** Listing 3.7 *** rem *** Listing 3.7 ***
@ -73,6 +74,7 @@ pages: 067-070
echo * An error occurred while building the long-period Zen timer. * echo * An error occurred while building the long-period Zen timer. *
echo *************************************************************** echo ***************************************************************
:end :end
```
Listing 3.8 shows sample code that can be timed with the test-bed Listing 3.8 shows sample code that can be timed with the test-bed
program of Listing 3.6. Listing 3.8 measures the time required to program of Listing 3.6. Listing 3.8 measures the time required to
@ -81,6 +83,7 @@ the precision Zen timer to handle on the 8088.
**LISTING 3.8 LST3-8.ASM** **LISTING 3.8 LST3-8.ASM**
```nasm
; ;
; Measures the performance of 20,000 loads of AL from ; Measures the performance of 20,000 loads of AL from
; memory. (Use by renaming to TESTCODE, which is ; memory. (Use by renaming to TESTCODE, which is
@ -108,11 +111,14 @@ the precision Zen timer to handle on the 8088.
; Stop timing. ; Stop timing.
; ;
callZTimerOff callZTimerOff
```
When LZTIME.BAT is run on a PC with the following command line (assuming When LZTIME.BAT is run on a PC with the following command line (assuming
the code in Listing 3.8 is the file LST3-8.ASM) the code in Listing 3.8 is the file LST3-8.ASM)
```sh
lztime lst3-8.asm lztime lst3-8.asm
```
the result is 72,544 µs, or about 3.63 µs per load of AL from memory. the result is 72,544 µs, or about 3.63 µs per load of AL from memory.
This is just slightly longer than the time per load of AL measured by This is just slightly longer than the time per load of AL measured by
@ -147,4 +153,6 @@ lines from Listing 3.1 that must be changed. These changes convert the
code to use C-style external label names and the small model C code code to use C-style external label names and the small model C code
segment. (In C++, use the "C" specifier, as in segment. (In C++, use the "C" specifier, as in
```c
extern "C" ZTimerOn(void); extern "C" ZTimerOn(void);
```

View file

@ -16,11 +16,13 @@ doesn't occur, and the linker can find the routines' C-style names.)
That's all it takes; after doing this, you'll be able to use the Zen That's all it takes; after doing this, you'll be able to use the Zen
timer from C, as, for example, in: timer from C, as, for example, in:
```c
ZTimerOn(): ZTimerOn():
for (i=0, x=0; i<100; i++) for (i=0, x=0; i<100; i++)
x += i; x += i;
ZTimerOff(); ZTimerOff();
ZTimerReport(); ZTimerReport();
```
(I'm talking about the precision timer here. The long-period (I'm talking about the precision timer here. The long-period
timer—Listing 3.5—requires the same modifications, but to different timer—Listing 3.5—requires the same modifications, but to different
@ -46,12 +48,16 @@ One important safety tip when modifying the Zen timer for use with large
code model C code: Watch out for optimizing assemblers! TASM actually code model C code: Watch out for optimizing assemblers! TASM actually
replaces replaces
```nasm
call far ptr ReferenceZTimerOn call far ptr ReferenceZTimerOn
```
with with
```nasm
push cs push cs
call near ptr ReferenceZTimerOn call near ptr ReferenceZTimerOn
```
(and likewise for **ReferenceZTimerOff** ), which works because (and likewise for **ReferenceZTimerOff** ), which works because
**ReferenceZTimerOn** is in the same segment as the calling code. This **ReferenceZTimerOn** is in the same segment as the calling code. This

View file

@ -53,11 +53,15 @@ byte-sized accesses. That's why the official instruction timings
indicate that for code running on an 8088 an additional 4 cycles are indicate that for code running on an 8088 an additional 4 cycles are
required for every word-sized access to a memory operand. For instance, required for every word-sized access to a memory operand. For instance,
```nasm
mov ax,word ptr [MemVar] mov ax,word ptr [MemVar]
```
takes 4 cycles longer to read the word at address **MemVar** than takes 4 cycles longer to read the word at address **MemVar** than
```nasm
mov al,byte ptr [MemVar] mov al,byte ptr [MemVar]
```
takes to read the byte at address **MemVar.** (Actually, the difference takes to read the byte at address **MemVar.** (Actually, the difference
between the two isn't very likely to be exactly 4 cycles, for reasons between the two isn't very likely to be exactly 4 cycles, for reasons
@ -72,11 +76,15 @@ prior to adding to it, and one to write the result of the addition back
to the destination operand—and thus incurs not one but two 4-cycle to the destination operand—and thus incurs not one but two 4-cycle
penalties. As a result penalties. As a result
```nasm
add word ptr [MemVar],ax add word ptr [MemVar],ax
```
takes about 8 cycles longer to execute than: takes about 8 cycles longer to execute than:
```nasm
add byte ptr [MemVar],al add byte ptr [MemVar],al
```
String instructions can suffer from the 8-bit bus cycle-eater to a String instructions can suffer from the 8-bit bus cycle-eater to a
greater extent than other instructions. Believe it or not, a single greater extent than other instructions. Believe it or not, a single
@ -114,6 +122,7 @@ in all.
**LISTING 4.1 LST4-1.ASM** **LISTING 4.1 LST4-1.ASM**
```nasm
; Measures the performance of a loop which uses a ; Measures the performance of a loop which uses a
; byte-sized memory variable as the loop counter. ; byte-sized memory variable as the loop counter.
; ;
@ -127,9 +136,11 @@ in all.
dec [Counter] dec [Counter]
jnz LoopTop jnz LoopTop
call ZTimerOff call ZTimerOff
```
**LISTING 4.2 LST4-2.ASM** **LISTING 4.2 LST4-2.ASM**
```nasm
; Measures the performance of a loop which uses a ; Measures the performance of a loop which uses a
; word-sized memory variable as the loop counter. ; word-sized memory variable as the loop counter.
; ;
@ -143,6 +154,7 @@ in all.
dec [Counter] dec [Counter]
jnz LoopTop jnz LoopTop
call ZTimerOff call ZTimerOff
```
I'd like to make a brief aside concerning code optimization in the I'd like to make a brief aside concerning code optimization in the
listings in this book. Throughout this book I've modeled the sample code listings in this book. Throughout this book I've modeled the sample code

View file

@ -16,12 +16,16 @@ strive to use byte-sized memory variables whenever possible. That does
manipulate a word-sized memory variable in preference to 1 word-sized manipulate a word-sized memory variable in preference to 1 word-sized
memory access, as, for instance, memory access, as, for instance,
```nasm
mov dl,byte ptr [MemVar] mov dl,byte ptr [MemVar]
mov dh,byte ptr [MemVar+1] mov dh,byte ptr [MemVar+1]
```
versus: versus:
```nasm
mov dx,word ptr [MemVar] mov dx,word ptr [MemVar]
```
Recall that every access to a memory byte takes at least 4 cycles; that Recall that every access to a memory byte takes at least 4 cycles; that
limitation is built right into the 8088. The 8088 is also built so that limitation is built right into the 8088. The 8088 is also built so that
@ -48,6 +52,7 @@ the second byte of each word.
**LISTING 4.3 LST4-3.ASM** **LISTING 4.3 LST4-3.ASM**
```nasm
; Measures the performance of reading 1,000 words ; Measures the performance of reading 1,000 words
; from memory with 1,000 word-sized accesses. ; from memory with 1,000 word-sized accesses.
; ;
@ -56,9 +61,11 @@ the second byte of each word.
call ZTimerOn call ZTimerOn
rep lodsw rep lodsw
call ZTimerOff call ZTimerOff
```
**LISTING 4.4 LST4-4.ASM** **LISTING 4.4 LST4-4.ASM**
```nasm
; Measures the performance of reading 1000 words ; Measures the performance of reading 1000 words
; from memory with 2,000 byte-sized accesses. ; from memory with 2,000 byte-sized accesses.
; ;
@ -67,6 +74,7 @@ the second byte of each word.
call ZTimerOn call ZTimerOn
rep lodsb rep lodsb
call ZTimerOff call ZTimerOff
```
In short, if you must perform a 16-bit memory access, let the 8088 break In short, if you must perform a 16-bit memory access, let the 8088 break
the access into two byte-sized accesses for you. The 8088 is more the access into two byte-sized accesses for you. The 8088 is more
@ -142,17 +150,21 @@ What makes the prefetch queue cycle-eater tricky is that it's
undocumented and unpredictable. That is, with a word-sized memory undocumented and unpredictable. That is, with a word-sized memory
access, such as access, such as
```nasm
mov [bx],ax mov [bx],ax
```
it's well-documented that an extra 4 cycles will always be required to it's well-documented that an extra 4 cycles will always be required to
write the upper byte of AX to memory. Not so with the prefetch queue write the upper byte of AX to memory. Not so with the prefetch queue
cycle-eater lurking nearby. For instance, the instructions cycle-eater lurking nearby. For instance, the instructions
```nasm
shr ax,1 shr ax,1
shr ax,1 shr ax,1
shr ax,1 shr ax,1
shr ax,1 shr ax,1
shr ax,1 shr ax,1
```
should execute in 10 cycles, since each **SHR** takes 2 cycles to should execute in 10 cycles, since each **SHR** takes 2 cycles to
execute, according to Intel's specifications. Those specifications execute, according to Intel's specifications. Those specifications

View file

@ -104,6 +104,7 @@ that the "true" execution time of **SHR** is 8.64 cycles.
**LISTING 4.5 LST4-5.ASM** **LISTING 4.5 LST4-5.ASM**
```nasm
; Measures the performance of 1,000 SHR instructions ; Measures the performance of 1,000 SHR instructions
; in a row. Since SHR executes in 2 cycles but is ; in a row. Since SHR executes in 2 cycles but is
; 2 bytes long, the prefetch queue is always empty, ; 2 bytes long, the prefetch queue is always empty,
@ -115,9 +116,11 @@ that the "true" execution time of **SHR** is 8.64 cycles.
shr ax,1 shr ax,1
endm endm
call ZTimerOff call ZTimerOff
```
**LISTING 4.6 LST4-6.ASM** **LISTING 4.6 LST4-6.ASM**
```nasm
; Measures the performance of 1,000 MUL/SHR instruction ; Measures the performance of 1,000 MUL/SHR instruction
; pairs in a row. The lengthy execution time of MUL ; pairs in a row. The lengthy execution time of MUL
; should keep the prefetch queue from ever emptying. ; should keep the prefetch queue from ever emptying.
@ -130,6 +133,7 @@ that the "true" execution time of **SHR** is 8.64 cycles.
shr ax,1 shr ax,1
endm endm
call ZTimerOff call ZTimerOff
```
![**Figure 4.3**  *Execution and instruction prefetching sequence for ![**Figure 4.3**  *Execution and instruction prefetching sequence for
Listing 4.5.*](images/04-03.jpg) Listing 4.5.*](images/04-03.jpg)

View file

@ -43,6 +43,7 @@ Listing 4.6.*](images/04-04.jpg)
**LISTING 4.7 LST4-7.ASM** **LISTING 4.7 LST4-7.ASM**
```nasm
; Measures the performance of repeated MOV AL,0 instructions, ; Measures the performance of repeated MOV AL,0 instructions,
; which take 4 cycles each according to Intel's official ; which take 4 cycles each according to Intel's official
; specifications. ; specifications.
@ -53,9 +54,11 @@ Listing 4.6.*](images/04-04.jpg)
mov al,0 mov al,0
endm endm
call ZTimerOff call ZTimerOff
```
**LISTING 4.8 LST4-8.ASM** **LISTING 4.8 LST4-8.ASM**
```nasm
; Measures the performance of repeated SUB AL,AL instructions, ; Measures the performance of repeated SUB AL,AL instructions,
; which take 3 cycles each according to Intel's official ; which take 3 cycles each according to Intel's official
; specifications. ; specifications.
@ -66,6 +69,7 @@ Listing 4.6.*](images/04-04.jpg)
sub al,al sub al,al
endm endm
call ZTimerOff call ZTimerOff
```
As you can see, it's easy to be drawn into thinking you're saving cycles As you can see, it's easy to be drawn into thinking you're saving cycles
when you're not. You can only improve the performance of a specific bit when you're not. You can only improve the performance of a specific bit

View file

@ -28,6 +28,7 @@ request a memory access from the Bus Interface Unit.)
**LISTING 4.9 LST4-9.ASM** **LISTING 4.9 LST4-9.ASM**
```nasm
; Measures the performance of repeated MUL instructions, ; Measures the performance of repeated MUL instructions,
; which allow the prefetch queue to be full at all times, ; which allow the prefetch queue to be full at all times,
; to demonstrate a case in which DRAM refresh has no impact ; to demonstrate a case in which DRAM refresh has no impact
@ -39,6 +40,7 @@ request a memory access from the Bus Interface Unit.)
mul ax mul ax
endm endm
call ZTimerOff call ZTimerOff
```
Running Listing 4.9, we find that each **MUL** executes in 24.72 µs, or Running Listing 4.9, we find that each **MUL** executes in 24.72 µs, or
exactly 118 cycles. Since that's the shortest time in which **MUL** can exactly 118 cycles. Since that's the shortest time in which **MUL** can
@ -56,6 +58,7 @@ to fetch the instruction bytes.
**LISTING 4.10 LST4-10.ASM** **LISTING 4.10 LST4-10.ASM**
```nasm
; Measures the performance of repeated SHR instructions, ; Measures the performance of repeated SHR instructions,
; which empty the prefetch queue, to demonstrate the ; which empty the prefetch queue, to demonstrate the
; worst-case impact of DRAM refresh on code performance. ; worst-case impact of DRAM refresh on code performance.
@ -65,6 +68,7 @@ to fetch the instruction bytes.
shr ax,1 shr ax,1
endm endm
call ZTimerOff call ZTimerOff
```
Since 4 cycles are required to read each instruction byte, we'd expect Since 4 cycles are required to read each instruction byte, we'd expect
each **SHR** to execute in 8 cycles, or 1.676 µs, if there were no DRAM each **SHR** to execute in 8 cycles, or 1.676 µs, if there were no DRAM

View file

@ -86,6 +86,7 @@ long as we had assumed, but a long time nonetheless.
**LISTING 4.11 LST4-11.ASM** **LISTING 4.11 LST4-11.ASM**
```nasm
; Times speed of memory access to Enhanced Graphics ; Times speed of memory access to Enhanced Graphics
; Adapter graphics mode display memory at A000:0000. ; Adapter graphics mode display memory at A000:0000.
; ;
@ -114,6 +115,7 @@ long as we had assumed, but a long time nonetheless.
; ;
mov ax,0003h mov ax,0003h
int 10h ;return to text mode int 10h ;return to text mode
```
For comparison, let's see how long the same code takes when accessing For comparison, let's see how long the same code takes when accessing
normal system RAM instead of display memory. The code in Listing 4.12, normal system RAM instead of display memory. The code in Listing 4.12,
@ -125,6 +127,7 @@ cycle-eater can *more than double* the execution time of 8088 code!
**LISTING 4.12 LST4-12.ASM** **LISTING 4.12 LST4-12.ASM**
```nasm
; Times speed of memory access to normal system ; Times speed of memory access to normal system
; memory. ; memory.
; ;
@ -143,6 +146,7 @@ cycle-eater can *more than double* the execution time of 8088 code!
; is just to measure memory access ; is just to measure memory access
; times ; times
call ZTimerOff call ZTimerOff
```
Bear in mind that we're talking about a worst case here; the impact of Bear in mind that we're talking about a worst case here; the impact of
the display adapter cycle-eater is proportional to the percent of time a the display adapter cycle-eater is proportional to the percent of time a

View file

@ -12,6 +12,7 @@ pages: 118-121
**LISTING 5.1 SEARCH.C** **LISTING 5.1 SEARCH.C**
```c
/* Program to search the file specified by the first command-line /* Program to search the file specified by the first command-line
* argument for the string specified by the second command-line * argument for the string specified by the second command-line
* argument. Performs the search by reading and searching blocks * argument. Performs the search by reading and searching blocks
@ -177,3 +178,4 @@ pages: 118-121
exit(Found); /* Return the found/not found status as the exit(Found); /* Return the found/not found status as the
DOS errorlevel */ DOS errorlevel */
} }
```

View file

@ -120,10 +120,14 @@ For example, suppose you have an array base address in BX and an index
into the array in SI. You could add the two registers together to into the array in SI. You could add the two registers together to
address memory, like this: address memory, like this:
```nasm
add bx,si add bx,si
mov al,[bx] mov al,[bx]
```
Or you could let the processor do the arithmetic for you in a single Or you could let the processor do the arithmetic for you in a single
instruction: instruction:
```nasm
mov al,[bx+si] mov al,[bx+si]
```

View file

@ -19,11 +19,13 @@ within a loop, however, it's advantageous on the 8088 CPU to perform the
addition outside the loop, if possible, reducing effective address addition outside the loop, if possible, reducing effective address
calculation time inside the loop, as in the following: calculation time inside the loop, as in the following:
```nasm
add bx,si add bx,si
LoopTop: LoopTop:
mov al,[bx] mov al,[bx]
inc bx inc bx
loop LoopTop loop LoopTop
```
Here, **MOV AL,[BX]** is two cycles faster than **MOV AL,[BX+SI]**. Here, **MOV AL,[BX]** is two cycles faster than **MOV AL,[BX+SI]**.
@ -73,24 +75,32 @@ source operands.
Imagine that we want to add BX to DI, add two to the result, and store Imagine that we want to add BX to DI, add two to the result, and store
the result in AX. The obvious solution is this: the result in AX. The obvious solution is this:
```nasm
mov ax,bx mov ax,bx
add ax,di add ax,di
add ax,2 add ax,2
```
(It would be more compact to increment AX twice than to add two to it, (It would be more compact to increment AX twice than to add two to it,
and would probably be faster on an 8088, but that's not what we're after and would probably be faster on an 8088, but that's not what we're after
at the moment.) An elegant alternative solution is simply: at the moment.) An elegant alternative solution is simply:
```nasm
lea ax,[bx+di+2] lea ax,[bx+di+2]
```
Likewise, either of the following would copy SI plus two to DI Likewise, either of the following would copy SI plus two to DI
```nasm
mov di,si mov di,si
add di,2 add di,2
```
or: or:
```nasm
lea di,[si+2] lea di,[si+2]
```
Mind you, the only components **LEA** can add are BX or BP, SI or DI, Mind you, the only components **LEA** can add are BX or BP, SI or DI,
and a constant displacement, so it's not going to replace **ADD** most and a constant displacement, so it's not going to replace **ADD** most
@ -135,14 +145,18 @@ bits, so we can now add up to two 32-bit registers and a constant, *and*
shift (or multiply) one of the registers to some extent—all with a shift (or multiply) one of the registers to some extent—all with a
single instruction. For example, single instruction. For example,
```nasm
lea edi,TableBase[ecx+edx*4] lea edi,TableBase[ecx+edx*4]
```
replaces all this replaces all this
```nasm
mov edi,edx mov edi,edx
shl edi,2 shl edi,2
add edi,ecx add edi,ecx
add edi,offset TableBase add edi,offset TableBase
```
when pointing to an entry in a doubly indexed table. when pointing to an entry in a doubly indexed table.
@ -156,15 +170,19 @@ index on the 386, and can be scaled as the index while being used
unchanged as the base. That means that you can, for example, multiply unchanged as the base. That means that you can, for example, multiply
EBX by 5 with: EBX by 5 with:
```nasm
lea ebx,[ebx+ebx*4] lea ebx,[ebx+ebx*4]
```
Without **LEA** and scaling, multiplication of EBX by 5 would require Without **LEA** and scaling, multiplication of EBX by 5 would require
either a relatively slow **MUL**, along with a set-up instruction or either a relatively slow **MUL**, along with a set-up instruction or
two, or three separate instructions along the lines of the following two, or three separate instructions along the lines of the following
```nasm
mov edx,ebx mov edx,ebx
shl ebx,2 shl ebx,2
add ebx,edx add ebx,edx
```
and would in either case require the destruction of the contents of and would in either case require the destruction of the contents of
another register. another register.

View file

@ -13,13 +13,17 @@ pages: 139-141
By the way, don't fall victim to the lures of **JCXZ** and do something By the way, don't fall victim to the lures of **JCXZ** and do something
like this: like this:
```nasm
and cx,ofh ;Isolate the desired field and cx,ofh ;Isolate the desired field
jcxz SkipLoop ;If field is 0, don't bother jcxz SkipLoop ;If field is 0, don't bother
```
The **AND** instruction has already set the Zero flag, so this The **AND** instruction has already set the Zero flag, so this
```nasm
and cx,0fh ;Isolate the desired field and cx,0fh ;Isolate the desired field
jz SkipLoop ;If field is 0, don't bother jz SkipLoop ;If field is 0, don't bother
```
will do just fine and is faster on all processors. Use **JCXZ** only will do just fine and is faster on all processors. Use **JCXZ** only
when the Zero flag isn't already set to reflect the status of CX. when the Zero flag isn't already set to reflect the status of CX.

View file

@ -12,6 +12,7 @@ pages: 141-143
**LISTING 7.1 L7-1.ASM** **LISTING 7.1 L7-1.ASM**
```nasm
; Program to illustrate searching through a buffer of a specified ; Program to illustrate searching through a buffer of a specified
; length until either a specified byte or a zero byte is ; length until either a specified byte or a zero byte is
; encountered. ; encountered.
@ -102,6 +103,7 @@ pages: 141-143
ret ret
SearchMaxLengthendp SearchMaxLengthendp
end Start end Start
```
### Unrolling Loops {#Heading7} ### Unrolling Loops {#Heading7}

View file

@ -12,6 +12,7 @@ pages: 143-145
**LISTING 7.2 L7-2.ASM** **LISTING 7.2 L7-2.ASM**
```nasm
; Program to illustrate searching through a buffer of a specified ; Program to illustrate searching through a buffer of a specified
; length until a specified zero byte is encountered. ; length until a specified zero byte is encountered.
; A loop unrolled four times and terminated with LOOP is used. ; A loop unrolled four times and terminated with LOOP is used.
@ -140,6 +141,7 @@ pages: 143-145
ret ret
SearchMaxLengthendp SearchMaxLengthendp
end Start end Start
```
How much difference? Listing 7.2 runs in 121 µs—40 percent faster than How much difference? Listing 7.2 runs in 121 µs—40 percent faster than
Listing 7.1, even though Listing 7.2 still uses **LOOP** rather than Listing 7.1, even though Listing 7.2 still uses **LOOP** rather than

View file

@ -19,9 +19,11 @@ simple task of setting bit N of AX to 1.
The obvious way to do this is to place N in CL, rotate the bit into The obvious way to do this is to place N in CL, rotate the bit into
position, and OR it with AX, as follows: position, and OR it with AX, as follows:
```nasm
MOV BX,1 MOV BX,1
SHL BX,CL SHL BX,CL
OR AX,BX OR AX,BX
```
This solution is obvious because it takes good advantage of the special This solution is obvious because it takes good advantage of the special
ability of the x86 family to shift or rotate by the variable number of ability of the x86 family to shift or rotate by the variable number of
@ -31,6 +33,7 @@ bit number in BX, and look the shifted bit up, as shown in Listing 7.3.
**LISTING 7.3 L7-3.ASM** **LISTING 7.3 L7-3.ASM**
```nasm
SHL BX,1 ;prepare for word sized look up SHL BX,1 ;prepare for word sized look up
OR AX,ShiftTable[BX] ;look up the bit and OR it in OR AX,ShiftTable[BX] ;look up the bit and OR it in
: :
@ -40,6 +43,7 @@ bit number in BX, and look the shifted bit up, as shown in Listing 7.3.
DW BIT_PATTERN DW BIT_PATTERN
BIT_PATTERN=BIT_PATTERN SHL 1 BIT_PATTERN=BIT_PATTERN SHL 1
ENDM ENDM
```
Even though it accesses memory, this approach takes only 20 cycles—more Even though it accesses memory, this approach takes only 20 cycles—more
than twice as fast as the variable shift. Once again, we were able to than twice as fast as the variable shift. Once again, we were able to
@ -55,6 +59,7 @@ code snippet in Listing 7.4.
**LISTING 7.4 L7-4.ASM** **LISTING 7.4 L7-4.ASM**
```nasm
OR EAX,ShiftTable[EBX*4] ;look up the bit and OR it in OR EAX,ShiftTable[EBX*4] ;look up the bit and OR it in
: :
ShiftTable LABEL DWORD ShiftTable LABEL DWORD
@ -63,6 +68,7 @@ code snippet in Listing 7.4.
DD BIT_PATTERN DD BIT_PATTERN
BIT_PATTERN=BIT_PATTERN SHL 1 BIT_PATTERN=BIT_PATTERN SHL 1
ENDM ENDM
```
> ![](images/i.jpg) > ![](images/i.jpg)
> Besides illustrating the advantages of local optimization, this example > Besides illustrating the advantages of local optimization, this example
@ -119,6 +125,7 @@ addition to the next.
**LISTING 7.5 L7-5.ASM** **LISTING 7.5 L7-5.ASM**
```nasm
CLC ;clear the Carry for the initial addition CLC ;clear the Carry for the initial addition
LOOP_TOP: LOOP_TOP:
MOV AX,[SI];get next source operand word MOV AX,[SI];get next source operand word
@ -128,12 +135,14 @@ addition to the next.
INC DI ;point to next dest operand word INC DI ;point to next dest operand word
INC DI INC DI
LOOP LOOP_TOP LOOP LOOP_TOP
```
If **ADD** were used, the Carry flag would have to be saved between If **ADD** were used, the Carry flag would have to be saved between
additions, with code along the lines shown in Listing 7.6. additions, with code along the lines shown in Listing 7.6.
**LISTING 7.6 L7-6.ASM** **LISTING 7.6 L7-6.ASM**
```nasm
CLC ;clear the carry for the initial addition CLC ;clear the carry for the initial addition
LOOP_TOP: LOOP_TOP:
MOV AX,[SI] ;get next source operand word MOV AX,[SI] ;get next source operand word
@ -143,6 +152,7 @@ additions, with code along the lines shown in Listing 7.6.
ADD DI,2 ;point to next dest operand word ADD DI,2 ;point to next dest operand word
SAHF ;restore the carry flag SAHF ;restore the carry flag
LOOP LOOP_TOP LOOP LOOP_TOP
```
It's not that the Listing 7.6 approach is necessarily better or worse; It's not that the Listing 7.6 approach is necessarily better or worse;
that depends on the processor and the situation. The Listing 7.6 that depends on the processor and the situation. The Listing 7.6
@ -182,13 +192,17 @@ an 8088 but are relatively slow instructions on the 486 and Pentium.
There are times when it's a clear liability that **INC** doesn't set the There are times when it's a clear liability that **INC** doesn't set the
Carry flag. For instance Carry flag. For instance
```nasm
INC AX INC AX
ADC DX,0 ADC DX,0
```
does *not* increment the 32-bit value in DX:AX. To do that, you'd need does *not* increment the 32-bit value in DX:AX. To do that, you'd need
the following: the following:
```nasm
ADD AX,1 ADD AX,1
ADC DX,0 ADC DX,0
```
As always, pay attention! As always, pay attention!

View file

@ -91,8 +91,10 @@ deal. You've eliminated the trappings of the compiler—the stack frame
and the restricted register usage—but you're still *thinking* like the and the restricted register usage—but you're still *thinking* like the
compiler. Try this: compiler. Try this:
```nasm
repnz scasw repnz scasw
jz Match jz Match
```
It's a simple example—but, I hope, a convincing one. Stretch your brain It's a simple example—but, I hope, a convincing one. Stretch your brain
when you optimize. when you optimize.

View file

@ -12,6 +12,7 @@ pages: 156-160
**LISTING 8.1 L8-1.C** **LISTING 8.1 L8-1.C**
```c
/* Program to search an array spanning a linked list of variable- /* Program to search an array spanning a linked list of variable-
sized blocks, for all entries with a specified ID number, sized blocks, for all entries with a specified ID number,
and return the average of the values of all such entries. Each of and return the average of the values of all such entries. Each of
@ -131,6 +132,7 @@ pages: 156-160
else else
return(IDMatchSum / IDMatchCount); return(IDMatchSum / IDMatchCount);
} }
```
The main body of Listing 8.1 constructs a linked list of memory blocks The main body of Listing 8.1 constructs a linked list of memory blocks
of various sizes and stores an array of structures across those blocks, of various sizes and stores an array of structures across those blocks,
@ -154,6 +156,7 @@ instruction can be used.
**LISTING 8.2 L8-2.COD** **LISTING 8.2 L8-2.COD**
```nasm
; Code generated by Microsoft C for inner loop of FindIDAverage. ; Code generated by Microsoft C for inner loop of FindIDAverage.
;|*** for (WorkingBlockCount=0; ;|*** for (WorkingBlockCount=0;
;|*** WorkingBlockCount<BlockPointer->BlockCount; ;|*** WorkingBlockCount<BlockPointer->BlockCount;
@ -183,3 +186,4 @@ instruction can be used.
mov WORD PTR [bp-2],di ;IDMatchSum mov WORD PTR [bp-2],di ;IDMatchSum
mov WORD PTR [bp-4],dx ;IDMatchCount mov WORD PTR [bp-4],dx ;IDMatchCount
$FB264: $FB264:
```

View file

@ -24,6 +24,7 @@ optimization, isn't it?
**LISTING 8.3 L8-3.ASM** **LISTING 8.3 L8-3.ASM**
```nasm
; Typically optimized assembly language version of FindIDAverage. ; Typically optimized assembly language version of FindIDAverage.
SearchedForID equ 4 ;Passed parameter offsets in the SearchedForID equ 4 ;Passed parameter offsets in the
BlockPointer equ 6 ; stack frame (skip over pushed BP BlockPointer equ 6 ; stack frame (skip over pushed BP
@ -37,6 +38,7 @@ optimization, isn't it?
.model small .model small
.code .code
public _FindIDAverage public _FindIDAverage
```
| | On 20 MHz 386 | On 10 MHz 286 | | | On 20 MHz 386 | On 10 MHz 286 |
|------------------------------------------------------------|------------------|------------------| |------------------------------------------------------------|------------------|------------------|
@ -47,6 +49,7 @@ optimization, isn't it?
Table: Table 8.1 Execution Times of FindIDAverage. Table: Table 8.1 Execution Times of FindIDAverage.
```nasm
_FindIDAverage proc near _FindIDAverage proc near
push bp ;Save caller's stack frame push bp ;Save caller's stack frame
mov bp,sp ;Point to our stack frame mov bp,sp ;Point to our stack frame
@ -90,6 +93,7 @@ Table: Table 8.1 Execution Times of FindIDAverage.
ret ret
_FindIDAverage ENDP _FindIDAverage ENDP
end end
```
Listing 8.4 tosses some sophisticated optimization techniques into the Listing 8.4 tosses some sophisticated optimization techniques into the
mix. The loop is unrolled eight times, eliminating a good deal of mix. The loop is unrolled eight times, eliminating a good deal of
@ -102,6 +106,7 @@ but not a tremendous return for the optimization effort invested.
**LISTING 8.4 L8-4.ASM** **LISTING 8.4 L8-4.ASM**
```nasm
; Heavily optimized assembly language version of FindIDAverage. ; Heavily optimized assembly language version of FindIDAverage.
; Features an unrolled loop and more efficient pointer use. ; Features an unrolled loop and more efficient pointer use.
SearchedForID equ 4 ;Passed parameter offsets in the SearchedForID equ 4 ;Passed parameter offsets in the
@ -190,3 +195,4 @@ but not a tremendous return for the optimization effort invested.
ret ret
_FindIDAverage ENDP _FindIDAverage ENDP
end end
```

View file

@ -20,6 +20,7 @@ merely rearranged.
**LISTING 8.5 L8-5.C** **LISTING 8.5 L8-5.C**
```c
/* Program to search an array spanning a linked list of variable- /* Program to search an array spanning a linked list of variable-
sized blocks, for all entries with a specified ID number, sized blocks, for all entries with a specified ID number,
and return the average of the values of all such entries. Each of and return the average of the values of all such entries. Each of
@ -38,9 +39,11 @@ merely rearranged.
void exit(int); void exit(int);
extern unsigned int FindIDAverage2(unsigned int, extern unsigned int FindIDAverage2(unsigned int,
struct BlockHeader *); struct BlockHeader *);
```
![**Figure 8.3**  *Linked array storage format (version 2).*](images/08-03.jpg) ![**Figure 8.3**  *Linked array storage format (version 2).*](images/08-03.jpg)
```c
/* Structure that starts each variable-sized block */ /* Structure that starts each variable-sized block */
struct BlockHeader { struct BlockHeader {
struct BlockHeader *NextBlock; /* Pointer to next block, or NULL struct BlockHeader *NextBlock; /* Pointer to next block, or NULL
@ -93,9 +96,11 @@ merely rearranged.
IDToFind, FindIDAverage2(IDToFind, BaseArrayBlockPointer)); IDToFind, FindIDAverage2(IDToFind, BaseArrayBlockPointer));
exit(0); exit(0);
} }
```
**LISTING 8.6 L8-6.ASM** **LISTING 8.6 L8-6.ASM**
```asm
; Alternative optimized assembly language version of FindIDAverage ; Alternative optimized assembly language version of FindIDAverage
; requires data organized as two arrays within each block rather ; requires data organized as two arrays within each block rather
; than as an array of two-value element structures. This allows the ; than as an array of two-value element structures. This allows the
@ -163,6 +168,7 @@ merely rearranged.
ret ret
_FindIDAverage2 ENDP _FindIDAverage2 ENDP
end end
```
The whole point of this rearrangement is to allow us to use **REP The whole point of this rearrangement is to allow us to use **REP
SCASW** to search through each block, and that's exactly what SCASW** to search through each block, and that's exactly what

View file

@ -88,13 +88,17 @@ machine word (32 bits in 386 protected mode, 16 bits otherwise), but it
renders **LEA** useless for multiword operations, which use the Carry renders **LEA** useless for multiword operations, which use the Carry
flag to tie together partial results. For example, these instructions flag to tie together partial results. For example, these instructions
```nasm
ADD EAX,EBX ADD EAX,EBX
ADC EDX,ECX ADC EDX,ECX
```
could *not* be replaced could *not* be replaced
```nasm
LEA EAX,[EAX+EBX] LEA EAX,[EAX+EBX]
ADC EDX,ECX ADC EDX,ECX
```
because **LEA** doesn't affect the Carry flag. because **LEA** doesn't affect the Carry flag.
@ -103,6 +107,7 @@ performing pointer arithmetic, however. For instance, the following code
uses **LEA** to advance the pointers while adding one 128-bit memory uses **LEA** to advance the pointers while adding one 128-bit memory
variable to another such variable: variable to another such variable:
```nasm
MOV ECX,4 ;# of 32-bit words to add MOV ECX,4 ;# of 32-bit words to add
CLC CLC
;no carry into the initial ADC ;no carry into the initial ADC
@ -113,6 +118,7 @@ variable to another such variable:
LEA ESI,[ESI+4] ;advance one array's pointer LEA ESI,[ESI+4] ;advance one array's pointer
LEA EDI,[EDI+4] ;advance the other array's pointer LEA EDI,[EDI+4] ;advance the other array's pointer
LOOP ADDLOOP LOOP ADDLOOP
```
(Yes, I could use **LODSD** instead of **MOV/LEA**; I'm just (Yes, I could use **LODSD** instead of **MOV/LEA**; I'm just
illustrating a point here. Besides, **LODS** is only 1 cycle faster than illustrating a point here. Besides, **LODS** is only 1 cycle faster than
@ -140,9 +146,11 @@ of my favorites:
John's code for setting AX to its absolute value is: John's code for setting AX to its absolute value is:
```nasm
CWD CWD
XOR AX,DX XOR AX,DX
SUB AX,DX SUB AX,DX
```
This does nothing when bit 15 of AX is 0 (that is, if AX is positive). This does nothing when bit 15 of AX is 0 (that is, if AX is positive).
When AX is negative, the code "nots" it and adds 1, which is exactly how When AX is negative, the code "nots" it and adds 1, which is exactly how
@ -150,10 +158,12 @@ you perform a two's complement negate. For the case where AX is not
negative, this trick usually beats the stuffing out of the standard negative, this trick usually beats the stuffing out of the standard
absolute value code: absolute value code:
```nasm
AND AX,AX ;negative? AND AX,AX ;negative?
JNS IsPositive ;no JNS IsPositive ;no
NEG AX ;yes,negate it NEG AX ;yes,negate it
IsPositive: IsPositive:
```
However, John's code is slower on a 486; as you're no doubt coming to However, John's code is slower on a 486; as you're no doubt coming to
realize (and as I'll explain in Chapters 12 and 13), the 486 is an realize (and as I'll explain in Chapters 12 and 13), the 486 is an
@ -162,19 +172,23 @@ optimization world unto itself.
Here's how John copies a block of bytes from DS:SI to ES:DI, moving as Here's how John copies a block of bytes from DS:SI to ES:DI, moving as
much data as possible a word at a time: much data as possible a word at a time:
```nasm
SHR CX,1 ;word count SHR CX,1 ;word count
REP MOVSW ;copy as many words as possible REP MOVSW ;copy as many words as possible
ADC CX,CX ;CX=1 if copy length was odd, ADC CX,CX ;CX=1 if copy length was odd,
;0 else ;0 else
REP MOVSB ;copy any odd byte REP MOVSB ;copy any odd byte
```
(**ADC CX,CX** can be replaced with **RCL CX,1**; which is faster (**ADC CX,CX** can be replaced with **RCL CX,1**; which is faster
depends on the processor type.) It might be hard to believe that the depends on the processor type.) It might be hard to believe that the
above is faster than this: above is faster than this:
```nasm
SHR CX,1 ;word count SHR CX,1 ;word count
REP MOVSW ;copy as many words as REP MOVSW ;copy as many words as
;possible ;possible
JNC CopyDone ;done if even copy length JNC CopyDone ;done if even copy length
MOVSB ;copy the odd byte MOVSB ;copy the odd byte
CopyDone: CopyDone:
```

View file

@ -27,6 +27,7 @@ video programmers are undoubtedly familiar with the following code to
multiply AX times 80 (the width in bytes of the bitmap in most PC multiply AX times 80 (the width in bytes of the bitmap in most PC
display modes): display modes):
```nasm
SHL AX,1 ;*2 SHL AX,1 ;*2
SH LAX,1 ;*4 SH LAX,1 ;*4
SH LAX,1 ;*8 SH LAX,1 ;*8
@ -35,26 +36,33 @@ display modes):
SH LAX,1 ;*32 SH LAX,1 ;*32
SH LAX,1 ;*64 SH LAX,1 ;*64
ADD AX,BX ;*80 ADD AX,BX ;*80
```
Using **LEA** on the 386, the above could be reduced to Using **LEA** on the 386, the above could be reduced to
```nasm
LEA EAX,[EAX*2] ;*2 LEA EAX,[EAX*2] ;*2
LEA EAX,[EAX*8] ;*16 LEA EAX,[EAX*8] ;*16
LEA EAX,[EAX+EAX*4] ;*80 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
```nasm
MOV EAX,MultiplesOf80Table[EAX*4] MOV EAX,MultiplesOf80Table[EAX*4]
```
but is close and takes a great deal less space. but is close and takes a great deal less space.
Of course, on the 386, the shift and add version could also be reduced Of course, on the 386, the shift and add version could also be reduced
to this considerably more efficient code: to this considerably more efficient code:
```nasm
SH LAX,4 ;*16 SH LAX,4 ;*16
MOV BX,AX MOV BX,AX
SHL AX,2 ;*64 SHL AX,2 ;*64
ADD AX,BX ;*80 ADD AX,BX ;*80
```
#### Speeding Up Multiplication {#Heading5} #### Speeding Up Multiplication {#Heading5}

View file

@ -40,6 +40,7 @@ using **REPZ CMPS** to check scanning matches.
**LISTING 9.1 L9-1.ASM** **LISTING 9.1 L9-1.ASM**
```nasm
; Searches a text buffer for a text string. Uses REPNZ SCASB to sca"n ; Searches a text buffer for a text string. Uses REPNZ SCASB to sca"n
; the buffer for locations that match the first character of the ; the buffer for locations that match the first character of the
; searched-for string, then uses REPZ CMPS to check fully only those ; searched-for string, then uses REPZ CMPS to check fully only those
@ -131,3 +132,4 @@ using **REPZ CMPS** to check scanning matches.
ret ret
_FindStringendp _FindStringendp
end end
```

View file

@ -12,6 +12,7 @@ pages: 178-180
**LISTING 9.2 L9-2.ASM** **LISTING 9.2 L9-2.ASM**
```nasm
; Searches a text buffer for a text string. Uses REPNZ SCASB to scan ; Searches a text buffer for a text string. Uses REPNZ SCASB to scan
; the buffer for locations that match a specified character of the ; the buffer for locations that match a specified character of the
; searched-for string, then uses REPZ CMPS to check fully only those ; searched-for string, then uses REPZ CMPS to check fully only those
@ -108,9 +109,11 @@ pages: 178-180
ret ret
_FindStringendp _FindStringendp
end end
```
**LISTING 9.3 L9-3.C** **LISTING 9.3 L9-3.C**
```c
/* Program to exercise buffer-search routines in Listings 9.1 & 9.2 */ /* Program to exercise buffer-search routines in Listings 9.1 & 9.2 */
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -143,3 +146,4 @@ pages: 178-180
strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH)); strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH));
} }
} }
```

View file

@ -43,7 +43,7 @@ one.
**LISTING 9.4 L9-4.ASM** **LISTING 9.4 L9-4.ASM**
. ```nasm
;-------------------------------------------------------------------------- ;--------------------------------------------------------------------------
; Sorts an array of ints. C callable (small model). 25 bytes. ; Sorts an array of ints. C callable (small model). 25 bytes.
; void sort( int num, int a[] ); ; void sort( int num, int a[] );
@ -75,6 +75,7 @@ one.
ret ret
end end
```
#### Full 32-Bit Division {#Heading8} #### Full 32-Bit Division {#Heading8}

View file

@ -12,6 +12,7 @@ pages: 182-185
**LISTING 9.5 L9-5.ASM** **LISTING 9.5 L9-5.ASM**
```nasm
; Divides an arbitrarily long unsigned dividend by a 16-bit unsigned ; Divides an arbitrarily long unsigned dividend by a 16-bit unsigned
; divisor. C near-callable as: ; divisor. C near-callable as:
; unsigned int Div(unsigned int * Dividend, ; unsigned int Div(unsigned int * Dividend,
@ -78,9 +79,11 @@ pages: 182-185
ret ret
_Divendp _Divendp
end end
```
**LISTING 9.6 L9-6.C** **LISTING 9.6 L9-6.C**
```c
/* Sample use of Div function to perform division when the result /* Sample use of Div function to perform division when the result
doesn't fit in 16 bits */ doesn't fit in 16 bits */
@ -97,6 +100,7 @@ pages: 182-185
k = Div((unsigned int *)&i, sizeof(i), j, (unsigned int *)&m); k = Div((unsigned int *)&i, sizeof(i), j, (unsigned int *)&m);
printf("%lu / %u = %lu r %u\n", i, j, m, k); printf("%lu / %u = %lu r %u\n", i, j, m, k);
} }
```
#### Sweet Spot Revisited {#Heading9} #### Sweet Spot Revisited {#Heading9}

View file

@ -52,11 +52,13 @@ PTR 1000:5**, but you'd be wrong. That won't even assemble. You might
then think to construct in memory a far pointer containing 1000:5, as in then think to construct in memory a far pointer containing 1000:5, as in
the following: the following:
```nasm
Ptr dd ? Ptr dd ?
: :
mov word ptr [Ptr],5 mov word ptr [Ptr],5
mov word ptr [Ptr+2],1000h mov word ptr [Ptr+2],1000h
jmp [Ptr] jmp [Ptr]
```
That will work, but at a price in performance. On an 8088, **JMP DWORD That will work, but at a price in performance. On an 8088, **JMP DWORD
PTR [*mem*]** (an indirect far jump) takes at least 37 cycles; **JMP PTR [*mem*]** (an indirect far jump) takes at least 37 cycles; **JMP
@ -87,6 +89,7 @@ solution; if you have one, pass it along.
**LISTING 9.7 L9-7.ASM** **LISTING 9.7 L9-7.ASM**
```nasm
; Program to perform a direct far jump to address 1000:5. ; Program to perform a direct far jump to address 1000:5.
; *** Do not run this program! It's just an example of how *** ; *** Do not run this program! It's just an example of how ***
; *** to build a direct far jump to an absolute address *** ; *** to build a direct far jump to an absolute address ***
@ -103,6 +106,7 @@ solution; if you have one, pass it along.
start: start:
jmp FarLabel jmp FarLabel
end start end start
```
By the way, if you're wondering how I figured this out, I merely applied By the way, if you're wondering how I figured this out, I merely applied
my good friend Dan Illowsky's long-standing rule for dealing with MASM: my good friend Dan Illowsky's long-standing rule for dealing with MASM:
@ -116,12 +120,16 @@ something that does—a rule with plenty of history on its side.
To finish up this chapter, consider these two items. First, in 32-bit To finish up this chapter, consider these two items. First, in 32-bit
protected mode, protected mode,
```nasm
sub eax,eax sub eax,eax
inc eax inc eax
```
takes 4 cycles to execute, but is only 3 bytes long, while takes 4 cycles to execute, but is only 3 bytes long, while
```nasm
mov eax,1 mov eax,1
```
takes only 2 cycles to execute, but is 5 bytes long (because native mode takes only 2 cycles to execute, but is 5 bytes long (because native mode
constants are dwords and the **MOV** instruction doesn't sign-extend). constants are dwords and the **MOV** instruction doesn't sign-extend).
@ -129,11 +137,15 @@ Both code fragments are ways to set **EAX** to 1 (although the first
affects the flags and the second doesn't); this is a classic trade-off affects the flags and the second doesn't); this is a classic trade-off
of speed for space. Second, of speed for space. Second,
```nasm
or ebx,-1 or ebx,-1
```
takes 2 cycles to execute and is 3 bytes long, while takes 2 cycles to execute and is 3 bytes long, while
```nasm
move bx,-1 move bx,-1
```
takes 2 cycles to execute and is 5 bytes long. Both instructions set takes 2 cycles to execute and is 5 bytes long. Both instructions set
**EBX** to -1; this is a classic trade-off of—gee, it's not a trade-off **EBX** to -1; this is a classic trade-off of—gee, it's not a trade-off

View file

@ -64,6 +64,7 @@ default optimization was used. All times measured with the Zen timer
**LISTING 10.1 L10-1.C** **LISTING 10.1 L10-1.C**
```c
/* Finds and returns the greatest common divisor of two positive /* Finds and returns the greatest common divisor of two positive
integers. Works by trying every integral divisor between the integers. Works by trying every integral divisor between the
smaller of the two integers and 1, until a divisor that divides smaller of the two integers and 1, until a divisor that divides
@ -86,6 +87,7 @@ default optimization was used. All times measured with the Zen timer
; ;
return(trial_divisor); return(trial_divisor);
} }
```
#### Wasted Breakthroughs {#Heading5} #### Wasted Breakthroughs {#Heading5}
@ -99,6 +101,7 @@ in Listing 10.2.
**LISTING 10.2 L10-2.C** **LISTING 10.2 L10-2.C**
```c
/* Finds and returns the greatest common divisor of two positive /* Finds and returns the greatest common divisor of two positive
integers. Works by subtracting the smaller integer from the integers. Works by subtracting the smaller integer from the
larger integer until either the values match (in which case larger integer until either the values match (in which case
@ -128,3 +131,4 @@ in Listing 10.2.
/* Now recursively call this function to continue the process */ /* Now recursively call this function to continue the process */
return(gcd(int1, int2)); return(gcd(int1, int2));
} }
```

View file

@ -45,6 +45,7 @@ Figure 10.3. Listing 10.3 is an implementation of Euclid's algorithm.
**LISTING 10.3 L10-3.C** **LISTING 10.3 L10-3.C**
```c
/* Finds and returns the greatest common divisor of two integers. /* Finds and returns the greatest common divisor of two integers.
Uses Euclid's algorithm: divides the larger integer by the Uses Euclid's algorithm: divides the larger integer by the
smaller; if the remainder is 0, the smaller integer is the GCD, smaller; if the remainder is 0, the smaller integer is the GCD,
@ -88,6 +89,7 @@ Figure 10.3. Listing 10.3 is an implementation of Euclid's algorithm.
continue the process */ continue the process */
return(gcd_recurs(smaller_int, temp)); return(gcd_recurs(smaller_int, temp));
} }
```
As you can see from Table 10.1, Euclid's algorithm is superior, As you can see from Table 10.1, Euclid's algorithm is superior,
especially for large numbers (and imagine if we were working with large especially for large numbers (and imagine if we were working with large
@ -129,6 +131,7 @@ recursive operations that Listing 10.3 does.
**LISTING 10.4 L10-4.C** **LISTING 10.4 L10-4.C**
```c
/* Finds and returns the greatest common divisor of two integers. /* Finds and returns the greatest common divisor of two integers.
Uses Euclid's algorithm: divides the larger integer by the Uses Euclid's algorithm: divides the larger integer by the
smaller; if the remainder is 0, the smaller integer is the GCD, smaller; if the remainder is 0, the smaller integer is the GCD,
@ -161,6 +164,7 @@ recursive operations that Listing 10.3 does.
int2 = temp; int2 = temp;
} }
} }
```
#### Patient Optimization {#Heading7} #### Patient Optimization {#Heading7}

View file

@ -12,6 +12,7 @@ pages: 200-203
**LISTING 10.5 L10-5.ASM** **LISTING 10.5 L10-5.ASM**
```nasm
; Finds and returns the greatest common divisor of two integers. ; Finds and returns the greatest common divisor of two integers.
; Uses Euclid's algorithm: divides the larger integer by the ; Uses Euclid's algorithm: divides the larger integer by the
; smaller; if the remainder is 0, the smaller integer is the GCD, ; smaller; if the remainder is 0, the smaller integer is the GCD,
@ -99,6 +100,7 @@ pages: 200-203
ret ret
_gcd endp _gcd endp
end end
```
Assembly language optimization is pattern matching on a local scale. Assembly language optimization is pattern matching on a local scale.
Frankly, it's also the sort of boring, brute-force work that people are Frankly, it's also the sort of boring, brute-force work that people are

View file

@ -97,13 +97,17 @@ than plain old registers, and can't be set to arbitrary values. That
means that segments can't be used for temporary storage or as part of a means that segments can't be used for temporary storage or as part of a
fast indivisible 32-bit load from memory, as in fast indivisible 32-bit load from memory, as in
```nasm
les ax,dword ptr [LongVar] les ax,dword ptr [LongVar]
mov dx,es mov dx,es
```
which loads **LongVar** into DX:AX faster than this: which loads **LongVar** into DX:AX faster than this:
```nasm
mov ax,word ptr [LongVar] mov ax,word ptr [LongVar]
mov dx,word ptr [LongVar+2] mov dx,word ptr [LongVar+2]
```
Protected mode uses those altered segment registers to offer access to a Protected mode uses those altered segment registers to offer access to a
great deal more memory than real mode: The 286 supports 16 megabytes of great deal more memory than real mode: The 286 supports 16 megabytes of

View file

@ -126,6 +126,7 @@ shortly.)
**LISTING 11.1 L11-1.ASM** **LISTING 11.1 L11-1.ASM**
```nasm
; ;
; *** Listing 11.1 *** ; *** Listing 11.1 ***
; ;
@ -145,6 +146,7 @@ shortly.)
mov [WordVar],0 mov [WordVar],0
endm endm
call ZTimerOff call ZTimerOff
```
What does this mean? It means that, practically speaking, the 286 as What does this mean? It means that, practically speaking, the 286 as
used in the AT doesn't have a 16-bit bus. From a performance used in the AT doesn't have a 16-bit bus. From a performance

View file

@ -101,6 +101,7 @@ refresh.
**LISTING 11.2 L11-2.ASM** **LISTING 11.2 L11-2.ASM**
```nasm
; ;
; *** Listing 11.2 *** ; *** Listing 11.2 ***
; ;
@ -118,6 +119,7 @@ refresh.
call ZTimerOn call ZTimerOn
rep movsw rep movsw
call ZTimerOff call ZTimerOff
```
On the other hand, Listing 11.3, which is exactly the same as Listing On the other hand, Listing 11.3, which is exactly the same as Listing
11.2 save that the memory accesses are word-aligned (start at even 11.2 save that the memory accesses are word-aligned (start at even
@ -128,6 +130,7 @@ we predicted.
**LISTING 11.3 L11-3.ASM** **LISTING 11.3 L11-3.ASM**
```nasm
; ;
; *** Listing 11.3 *** ; *** Listing 11.3 ***
; ;
@ -144,6 +147,7 @@ we predicted.
call ZTimerOn call ZTimerOn
rep movsw rep movsw
call ZTimerOff call ZTimerOff
```
The data alignment cycle-eater has intriguing implications for speeding The data alignment cycle-eater has intriguing implications for speeding
up 286/386 code. The expenditure of a little care and a few bytes to up 286/386 code. The expenditure of a little care and a few bytes to

View file

@ -15,12 +15,13 @@ AT clone to verify the basic functionality of the timer by measuring the
performance of simple instruction sequences. I was cruising along with performance of simple instruction sequences. I was cruising along with
no problems until I timed the following code: no problems until I timed the following code:
```nasm
mov cx,1000 mov cx,1000
call ZTimerOn call ZTimerOn
LoopTop: LoopTop:
loop LoopTop loop LoopTop
call ZTimerOff call ZTimerOff
```
![**Figure 11.2**  *Word-aligned prefetching on the 286.*](images/11-02.jpg) ![**Figure 11.2**  *Word-aligned prefetching on the 286.*](images/11-02.jpg)
@ -39,12 +40,14 @@ resided at the start of the next word-aligned word.
One simple change brought the execution time down to a reasonable 12.5 One simple change brought the execution time down to a reasonable 12.5
cycles per loop: cycles per loop:
```nasm
mov cx,1000 mov cx,1000
call ZTimerOn call ZTimerOn
even even
LoopTop: LoopTop:
loop LoopTop loop LoopTop
call ZTimerOff call ZTimerOff
```
While word-aligning branch destinations can improve branching While word-aligning branch destinations can improve branching
performance, it's a nuisance and can increase code size a good deal, so performance, it's a nuisance and can increase code size a good deal, so
@ -60,9 +63,11 @@ branch destination.
I recommend that you only go out of your way to word-align the start I recommend that you only go out of your way to word-align the start
offsets of your subroutines, as in: offsets of your subroutines, as in:
```nasm
even even
FindChar proc near FindChar proc near
: :
```
In my experience, this simple practice is the one form of code alignment In my experience, this simple practice is the one form of code alignment
that consistently provides a reasonable return for bytes and effort that consistently provides a reasonable return for bytes and effort

View file

@ -113,6 +113,7 @@ Theory confirmed.
**LISTING 11.4 L11-4.ASM** **LISTING 11.4 L11-4.ASM**
```nasm
; ;
; *** Listing 11.4 *** ; *** Listing 11.4 ***
; ;
@ -125,3 +126,4 @@ Theory confirmed.
add dx,100h add dx,100h
endm endm
call ZTimerOff call ZTimerOff
```

View file

@ -12,6 +12,7 @@ pages: 224-226
**LISTING 11.5 L11-5.ASM** **LISTING 11.5 L11-5.ASM**
```nasm
; ;
; *** Listing 11.5 *** ; *** Listing 11.5 ***
; ;
@ -31,6 +32,7 @@ pages: 224-226
add [WordVar]100h add [WordVar]100h
endm endm
call ZTimerOff call ZTimerOff
```
What's going on? Simply this: Instruction fetching is controlling What's going on? Simply this: Instruction fetching is controlling
overall execution time on *both* processors. Both the 8088 in a PC and overall execution time on *both* processors. Both the 8088 in a PC and

View file

@ -43,6 +43,7 @@ a segment and an offset. We'll also branch backward so that the address
pushed on the stack will point to the instruction we want to continue pushed on the stack will point to the instruction we want to continue
with. The code works out like this: with. The code works out like this:
```nasm
jmpshort popfskip jmpshort popfskip
popfiret: popfiret:
iret; branches to the instruction after the iret; branches to the instruction after the
@ -59,6 +60,7 @@ with. The code works out like this:
; the word that was on top of the stack when JMP SHORT POPFSKIP ; the word that was on top of the stack when JMP SHORT POPFSKIP
; was reached has been popped into the FLAGS register, just as ; was reached has been popped into the FLAGS register, just as
; if a POPF instruction had been executed. ; if a POPF instruction had been executed.
```
![**Figure 11.5**  *The operation of IRET.*](images/11-05.jpg) ![**Figure 11.5**  *The operation of IRET.*](images/11-05.jpg)
@ -68,6 +70,7 @@ The **POPF** workaround can best be implemented as a macro; we can also
emulate a far call by pushing CS and performing a near call, thereby emulate a far call by pushing CS and performing a near call, thereby
shrinking the workaround code by 1 byte: shrinking the workaround code by 1 byte:
```nasm
EMULATE_POPF macro EMULATE_POPF macro
local popfskip, popfiret local popfskip, popfiret
jmp short popfskip jmp short popfskip
@ -77,17 +80,20 @@ shrinking the workaround code by 1 byte:
push cs push cs
call popfiret call popfiret
endm endm
```
By the way, the flags can be popped much more quickly if you're willing By the way, the flags can be popped much more quickly if you're willing
to alter a register in the process. For example, the following macro to alter a register in the process. For example, the following macro
emulates **POPF** with just one branch, but wipes out AX: emulates **POPF** with just one branch, but wipes out AX:
```nasm
EMULATE_POPF_TRASH_AX macro EMULATE_POPF_TRASH_AX macro
push cs push cs
mov ax,offset $+5 mov ax,offset $+5
push ax push ax
iret iret
endm endm
```
It's not a perfect substitute for **POPF**, since **POPF** doesn't alter It's not a perfect substitute for **POPF**, since **POPF** doesn't alter
any registers, but it's faster and shorter than **EMULATE\_POPF** when any registers, but it's faster and shorter than **EMULATE\_POPF** when
@ -96,6 +102,7 @@ you can use which is shorter still, alters no registers, and branches
just once. (Of course, this version of **EMULATE\_POPF** won't work on just once. (Of course, this version of **EMULATE\_POPF** won't work on
an 8088.) an 8088.)
```nasm
.286 .286
: :
EMULATE_POPFmacro EMULATE_POPFmacro
@ -103,6 +110,7 @@ an 8088.)
pushoffset $+4 pushoffset $+4
iret iret
endm endm
```
![**Figure 11.6**  *Workaround code for the POPF bug.*](images/11-06.jpg) ![**Figure 11.6**  *Workaround code for the POPF bug.*](images/11-06.jpg)

View file

@ -128,14 +128,17 @@ matter what register is used.) **MOV AX, [BX+DI]** and **MOV CL,
As an example, you might adhere to this rule by replacing the code As an example, you might adhere to this rule by replacing the code
```nasm
LoopTop: LoopTop:
add ax,[bx+si] add ax,[bx+si]
add si,2 add si,2
dec cx dec cx
jnz LoopTop jnz LoopTop
```
with this with this
```nasm
add si,bx add si,bx
LoopTop: LoopTop:
add ax,[si] add ax,[si]
@ -143,3 +146,4 @@ with this
dec cx dec cx
jnz LoopTop jnz LoopTop
sub si,bx sub si,bx
```

View file

@ -58,8 +58,10 @@ isn't known until the instruction starts, and that's exactly the case
when the preceding instruction modifies one of the target instruction's when the preceding instruction modifies one of the target instruction's
addressing registers. For example, in the code addressing registers. For example, in the code
```nasm
MOV BX,OFFSET MemVar MOV BX,OFFSET MemVar
MOV AX,[BX] MOV AX,[BX]
```
there's no way that the 486 can calculate the address referenced by there's no way that the 486 can calculate the address referenced by
**MOV AX,[BX]** until **MOV BX,OFFSET MemVar** finishes, so pipelining **MOV AX,[BX]** until **MOV BX,OFFSET MemVar** finishes, so pipelining
@ -68,19 +70,23 @@ rearranging your code so that at least one instruction lies between the
loading of the memory pointer and its use. For example, loading of the memory pointer and its use. For example,
postdecrementing, as in the following postdecrementing, as in the following
```nasm
LoopTop: LoopTop:
add ax,[si] add ax,[si]
add si,2 add si,2
dec cx dec cx
jnz LoopTop jnz LoopTop
```
is faster than preincrementing, as in: is faster than preincrementing, as in:
```nasm
LoopTop: LoopTop:
add si,2 add si,2
add ax,[SI] add ax,[SI]
dec cx dec cx
jnz LoopTop jnz LoopTop
```
Now that we understand what Intel means by this rule, let me make a very Now that we understand what Intel means by this rule, let me make a very
important comment: My observations indicate that for real-mode code, the important comment: My observations indicate that for real-mode code, the
@ -108,28 +114,34 @@ instructions, but, because some 486 instructions take more than 1 cycle,
the 2 are not always equivalent) before it's used to point to memory, 1 the 2 are not always equivalent) before it's used to point to memory, 1
cycle is lost. Therefore, whereas this code cycle is lost. Therefore, whereas this code
```nasm
mov bx,offset MemVar mov bx,offset MemVar
mov ax,[bx] mov ax,[bx]
inc dx inc dx
dec cx dec cx
jnz LoopTop jnz LoopTop
```
loses two cycles from interrupting the address calculation pipeline, loses two cycles from interrupting the address calculation pipeline,
this code this code
```nasm
mov bx,offset MemVar mov bx,offset MemVar
inc dx inc dx
mov ax,[bx] mov ax,[bx]
dec cx dec cx
jnz LoopTop jnz LoopTop
```
loses only one cycle, and this code loses only one cycle, and this code
```nasm
mov bx,offset MemVar mov bx,offset MemVar
inc dx inc dx
dec cx dec cx
mov ax,[bx] mov ax,[bx]
jnz LoopTop jnz LoopTop
```
loses no cycles at all. Apparently, the 486's addressing calculation loses no cycles at all. Apparently, the 486's addressing calculation
pipeline actually starts 2 cycles ahead, as shown in Figure 12.2. (In pipeline actually starts 2 cycles ahead, as shown in Figure 12.2. (In

View file

@ -41,22 +41,26 @@ pops up only spottily when the stack pointer is involved.
For example, you'd certainly expect a sequence such as For example, you'd certainly expect a sequence such as
```nasm
: :
pop ax pop ax
ret ret
pop ax pop ax
et et
: :
```
to exhibit the addressing pipeline interruption phenomenon (SP is both to exhibit the addressing pipeline interruption phenomenon (SP is both
destination and addressing register for both instructions, according to destination and addressing register for both instructions, according to
Intel), but this code runs in six cycles per **POP/RET** pair, matching Intel), but this code runs in six cycles per **POP/RET** pair, matching
the official execution times exactly. Likewise, a sequence like the official execution times exactly. Likewise, a sequence like
```nasm
pop dx pop dx
pop cx pop cx
pop bx pop bx
pop ax pop ax
```
runs in one cycle per instruction, just as it should. runs in one cycle per instruction, just as it should.
@ -65,15 +69,19 @@ destination—for example, to deallocate local variables—and then using
**PUSH**, **POP**, or **RET**, definitely can interrupt the addressing **PUSH**, **POP**, or **RET**, definitely can interrupt the addressing
pipeline. For example pipeline. For example
```nasm
add sp,10h add sp,10h
ret ret
```
loses two cycles because SP is the explicit destination of one loses two cycles because SP is the explicit destination of one
instruction and then the implied addressing register for the next, and instruction and then the implied addressing register for the next, and
the sequence the sequence
```nasm
add sp,10h add sp,10h
pop ax pop ax
```
loses two cycles for the same reason. loses two cycles for the same reason.
@ -101,22 +109,26 @@ during the next instruction.
So, for example, it would be a bad idea to do this So, for example, it would be a bad idea to do this
```nasm
mov ah,o mov ah,o
: :
mov cx,[MemVar1] mov cx,[MemVar1]
mov al,[MemVar2] mov al,[MemVar2]
add cx,ax add cx,ax
```
because AL is loaded by one instruction, then AX is used as the source because AL is loaded by one instruction, then AX is used as the source
register for the next instruction. A cycle can be saved simply by register for the next instruction. A cycle can be saved simply by
rearranging the instructions so that the byte register load isn't rearranging the instructions so that the byte register load isn't
immediately followed by the word register usage, like so: immediately followed by the word register usage, like so:
```nasm
mov ah,o mov ah,o
: :
mov al,[MemVar2] mov al,[MemVar2]
mov cx,[MemVar1] mov cx,[MemVar1]
add cx,ax add cx,ax
```
Strange as it may seem, this rule is neither arbitrary nor nonsensical. Strange as it may seem, this rule is neither arbitrary nor nonsensical.
Basically, when a byte destination register is part of a word source Basically, when a byte destination register is part of a word source
@ -135,10 +147,12 @@ works.
In case you're curious, there's no such penalty for the typical **XLAT** In case you're curious, there's no such penalty for the typical **XLAT**
sequence like sequence like
```nasm
mov bx,offset MemTable mov bx,offset MemTable
: :
mov al,[si] mov al,[si]
xlat xlat
```
even though AL must be converted to a word by **XLAT** before it can be even though AL must be converted to a word by **XLAT** before it can be
added to BX and used to address memory. In fact, none of the penalties added to BX and used to address memory. In fact, none of the penalties

View file

@ -24,9 +24,11 @@ This, the last of this chapter's rules, is the strangest of the lot. If
any byte register is loaded, and then two cycles later any register is any byte register is loaded, and then two cycles later any register is
used to point to memory, one cycle is lost. So, for example, this code used to point to memory, one cycle is lost. So, for example, this code
```nasm
mov al,bl mov al,bl
mov cx,dx mov cx,dx
mov si,[di] mov si,[di]
```
takes four rather than the expected three cycles to execute. Note that takes four rather than the expected three cycles to execute. Note that
it is *not* required that the byte register be part of the register used it is *not* required that the byte register be part of the register used
@ -35,17 +37,20 @@ to address memory; any byte register will do the trick.
Worse still, loading byte registers both one and two cycles before a Worse still, loading byte registers both one and two cycles before a
register is used to address memory costs two cycles, as in register is used to address memory costs two cycles, as in
```nasm
mov bl,al mov bl,al
mov cl,3 mov cl,3
mov bx,[si] mov bx,[si]
```
which takes five rather than three cycles to run. However, there is *no* which takes five rather than three cycles to run. However, there is *no*
penalty if a byte register is loaded one cycle but not two cycles before penalty if a byte register is loaded one cycle but not two cycles before
a register is used to address memory. Therefore, a register is used to address memory. Therefore,
```nasm
mov cx,3 mov cx,3
mov dl,al mov dl,al
mov si,[bx] mov si,[bx]
```
runs in the expected three cycles. runs in the expected three cycles.
@ -57,9 +62,11 @@ its interaction with the other rules—could lead to considerable
performance loss in seemingly air-tight code. For instance, a casual performance loss in seemingly air-tight code. For instance, a casual
observer would expect the following code to run in 3 cycles: observer would expect the following code to run in 3 cycles:
```nasm
mov bx,offset MemVar mov bx,offset MemVar
mov cl,al mov cl,al
mov ax,[bx] mov ax,[bx]
```
A more sophisticated programmer would expect to lose one cycle, because A more sophisticated programmer would expect to lose one cycle, because
BX is loaded two cycles before being used to address memory. In fact, BX is loaded two cycles before being used to address memory. In fact,
@ -97,6 +104,7 @@ addressing memory.
**LISTING 12.1 LST12-1.ASM** **LISTING 12.1 LST12-1.ASM**
```nasm
; Measures the effect of loading a byte register 2 cycles before ; Measures the effect of loading a byte register 2 cycles before
; using a register to address memory. ; using a register to address memory.
mov bp,2 ;run the test code twice to make sure mov bp,2 ;run the test code twice to make sure
@ -114,9 +122,11 @@ addressing memory.
jz Done jz Done
jmp CacheFillLoop jmp CacheFillLoop
Done: Done:
```
**LISTING 12.2 LST12-2.ASM** **LISTING 12.2 LST12-2.ASM**
```nasm
; Measures the effect of loading a byte register 1 cycle before ; Measures the effect of loading a byte register 1 cycle before
; using a register to address memory. ; using a register to address memory.
mov bp,2 ;run the test code twice to make sure mov bp,2 ;run the test code twice to make sure
@ -134,6 +144,7 @@ addressing memory.
jz Done jz Done
jmp CacheFillLoop jmp CacheFillLoop
Done: Done:
```
Note that Listings 12.1 and 12.2 each repeat the timing of the code Note that Listings 12.1 and 12.2 each repeat the timing of the code
under test a second time, to make sure that the instructions are in the under test a second time, to make sure that the instructions are in the

View file

@ -78,10 +78,12 @@ also lines, at a rate of three instructions for every two characters!
**LISTING 13.1 L13-1.ASM** **LISTING 13.1 L13-1.ASM**
```nasm
mov di,[bp+OFFS] ;get the next pair of characters mov di,[bp+OFFS] ;get the next pair of characters
mov bl,[di] ;get the state value for the pair mov bl,[di] ;get the state value for the pair
add dx,[bx+8000h] ;increment word and line count add dx,[bx+8000h] ;increment word and line count
; appropriately for the pair ; appropriately for the pair
```
Listing 13.1 looks as tight as it could be, with just two one-cycle Listing 13.1 looks as tight as it could be, with just two one-cycle
instructions, one two-cycle instruction, and no branches. It *is* tight, instructions, one two-cycle instruction, and no branches. It *is* tight,
@ -113,10 +115,12 @@ the intervening instruction takes two cycles, there's no penalty at all.
**LISTING 13.2 L13-2.ASM** **LISTING 13.2 L13-2.ASM**
```nasm
mov bl,[di] ;get the state value for the pair mov bl,[di] ;get the state value for the pair
mov di,[bp+OFFS] ;get the next pair of characters mov di,[bp+OFFS] ;get the next pair of characters
add dx,[bx+8000h] ;increment word and line count add dx,[bx+8000h] ;increment word and line count
; appropriately for the pair ; appropriately for the pair
```
At this point, Terje had nearly doubled the performance of this code At this point, Terje had nearly doubled the performance of this code
simply by moving one instruction. (Note that swapping the instructions simply by moving one instruction. (Note that swapping the instructions

View file

@ -22,18 +22,22 @@ throughput of two cycles/char."
**LISTING 13.3 L13-3.ASM** **LISTING 13.3 L13-3.ASM**
```nasm
mov bl,[di] ;get the state value for the pair mov bl,[di] ;get the state value for the pair
mov di,[bp+OFFS] ;get the next pair of characters mov di,[bp+OFFS] ;get the next pair of characters
mov ax,[bx+8000h] ;increment word and line count mov ax,[bx+8000h] ;increment word and line count
add dx,ax ; appropriately for the pair add dx,ax ; appropriately for the pair
```
**LISTING 13.4 L13-4.ASM** **LISTING 13.4 L13-4.ASM**
```nasm
mov bl,[di] ;get the state value for the pair mov bl,[di] ;get the state value for the pair
mov di,[bp+OFFS] ;get the next pair of characters mov di,[bp+OFFS] ;get the next pair of characters
add dx,ax ;increment word and line count add dx,ax ;increment word and line count
; appropriately for the pair ; appropriately for the pair
mov ax,[bx+8000h] ;get increments for next time mov ax,[bx+8000h] ;get increments for next time
```
I'd like to point out two fairly remarkable things. First, the single I'd like to point out two fairly remarkable things. First, the single
cycle that Terje saved in Listing 13.4 sped up his entire word-counting cycle that Terje saved in Listing 13.4 sped up his entire word-counting
@ -57,9 +61,11 @@ and 16-bit registers are not valid operands.) The obvious use of
first in memory, also called *little endian*) to Motorola format (most first in memory, also called *little endian*) to Motorola format (most
significant byte first in memory, or *big endian*), like so: significant byte first in memory, or *big endian*), like so:
```nasm
lodsd lodsd
bswap bswap
stosd stosd
```
**BSWAP** can also be useful for reversing the order of pixel bits from **BSWAP** can also be useful for reversing the order of pixel bits from
a bitmap so that they can be rotated 32 bits at a time with an a bitmap so that they can be rotated 32 bits at a time with an
@ -94,6 +100,7 @@ memory, isn't it?
**LISTING 13.5 L13-5.ASM** **LISTING 13.5 L13-5.ASM**
```nasm
mov cx,[initialskip] mov cx,[initialskip]
shl ecx,16 ;put skip value in upper half of ECX shl ecx,16 ;put skip value in upper half of ECX
mov cx,100 ;put loop count in CX mov cx,100 ;put loop count in CX
@ -105,6 +112,7 @@ memory, isn't it?
ror ecx,16 ;put loop count in CX ror ecx,16 ;put loop count in CX
dec cx ;count down loop dec cx ;count down loop
jnz looptop jnz looptop
```
Not necessarily. Shifts and rotates are among the worst performing Not necessarily. Shifts and rotates are among the worst performing
instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes

View file

@ -21,6 +21,7 @@ registers into one 32-bit register much more useful.
**LISTING 13.6 L13-6.ASM** **LISTING 13.6 L13-6.ASM**
```nasm
mov cx,[initialskip] mov cx,[initialskip]
bswap ecx ;put skip value in upper half of ECX bswap ecx ;put skip value in upper half of ECX
mov cx,100 ;put loop count in CX mov cx,100 ;put loop count in CX
@ -32,6 +33,7 @@ registers into one 32-bit register much more useful.
bswap ecx ;put loop count in CX bswap ecx ;put loop count in CX
dec cx ;count down loop dec cx ;count down loop
jnz looptop jnz looptop
```
### Pushing and Popping Memory {#Heading5} ### Pushing and Popping Memory {#Heading5}
@ -44,12 +46,16 @@ contrast, loading a memory location into a register takes only one
cycle, and pushing a register takes just 1 more cycle, for a total of cycle, and pushing a register takes just 1 more cycle, for a total of
two cycles. Therefore, two cycles. Therefore,
```nasm
mov ax,[bx] mov ax,[bx]
push ax push ax
```
is twice as fast as is twice as fast as
```nasm
push word ptr [bx] push word ptr [bx]
```
and the only cost is that the previous contents of AX are destroyed. and the only cost is that the previous contents of AX are destroyed.
@ -98,9 +104,11 @@ disassembly in a debugger or by having the assembler generate a listing
file. You could then insert the n-bit version of **SHL AX,1** in your file. You could then insert the n-bit version of **SHL AX,1** in your
code as follows: code as follows:
```nasm
mov ax,1 mov ax,1
db 0c1h, 0e0h, 001h db 0c1h, 0e0h, 001h
mov dx,ax mov dx,ax
```
At the end of this sequence, DX will contain 2, and the fast n-bit At the end of this sequence, DX will contain 2, and the fast n-bit
version of **SHL AX,1** will have executed. If you use this approach, version of **SHL AX,1** will have executed. If you use this approach,

View file

@ -17,7 +17,9 @@ register may serve as the base memory addressing register, and almost
any register may serve as the potentially scaled index register. For any register may serve as the potentially scaled index register. For
example, example,
```nasm
mov al,BaseTable[ecx+edx*4] mov al,BaseTable[ecx+edx*4]
```
uses a perfectly valid 32-bit address, with the byte accessed being the uses a perfectly valid 32-bit address, with the byte accessed being the
one at the offset in DS pointed to by the sum of EDX times 4 plus the one at the offset in DS pointed to by the sum of EDX times 4 plus the
@ -82,14 +84,17 @@ registers when they're needed, but if you find yourself using them
inside key loops, you should see if it's possible to move the index inside key loops, you should see if it's possible to move the index
calculation outside the loop as, for example, in a loop like this: calculation outside the loop as, for example, in a loop like this:
```nasm
LoopTop: LoopTop:
add ax,DataTable[ebx*2] add ax,DataTable[ebx*2]
inc ebx inc ebx
dec cx dec cx
jnz LoopTop jnz LoopTop
```
You could change this to the following for greater performance: You could change this to the following for greater performance:
```nasm
add ebx,ebx ;ebx*2 add ebx,ebx ;ebx*2
LoopTop: LoopTop:
add ax,DataTable[ebx] add ax,DataTable[ebx]
@ -97,6 +102,7 @@ You could change this to the following for greater performance:
dec cx dec cx
jnz LoopTop jnz LoopTop
shr ebx,1 ;ebx*2/2 shr ebx,1 ;ebx*2/2
```
I'll end this chapter with two more quirks of 32-bit addressing. First, I'll end this chapter with two more quirks of 32-bit addressing. First,
as with 16-bit addressing, addressing that uses EBP as a base register as with 16-bit addressing, addressing that uses EBP as a base register

View file

@ -12,6 +12,7 @@ pages: 268-271
**LISTING 14.1 L14-1.C** **LISTING 14.1 L14-1.C**
```c
/* Searches a buffer for a specified pattern. In case of a mismatch, /* Searches a buffer for a specified pattern. In case of a mismatch,
uses the value of the mismatched byte to skip across as many uses the value of the mismatched byte to skip across as many
potential match locations as possible (partial Boyer-Moore). potential match locations as possible (partial Boyer-Moore).
@ -97,9 +98,11 @@ pages: 268-271
BufferPtr += Skip; BufferPtr += Skip;
} }
} }
```
**LISTING 14.2 L14-2.C** **LISTING 14.2 L14-2.C**
```c
/* Program to exercise buffer-search routines in Listings 14.1 & 14.3. /* Program to exercise buffer-search routines in Listings 14.1 & 14.3.
(Must be modified to put copy of pattern as sentinel at end of the (Must be modified to put copy of pattern as sentinel at end of the
search buffer in order to be used with Listing 14.4.) */ search buffer in order to be used with Listing 14.4.) */
@ -154,6 +157,7 @@ pages: 268-271
} }
exit(0); exit(0);
} }
```
Well, architecture carries a lot of weight, but it sure as heck isn't Well, architecture carries a lot of weight, but it sure as heck isn't
destiny. I had simply fallen into the trap of figuring that the destiny. I had simply fallen into the trap of figuring that the

View file

@ -12,6 +12,7 @@ pages: 271-274
**LISTING 14.3 L14-3.ASM** **LISTING 14.3 L14-3.ASM**
```nasm
; Searches a buffer for a specified pattern. In case of a mismatch, ; Searches a buffer for a specified pattern. In case of a mismatch,
; uses the value of the mismatched byte to skip across as many ; uses the value of the mismatched byte to skip across as many
; potential match locations as possible (partial Boyer-Moore). ; potential match locations as possible (partial Boyer-Moore).
@ -172,3 +173,4 @@ pages: 271-274
ret ret
_FindString endp _FindString endp
end end
```

View file

@ -35,6 +35,7 @@ about 60 percent faster than Listing 14.3.
**LISTING 14.4 L14-4.ASM** **LISTING 14.4 L14-4.ASM**
```nasm
; Searches a buffer for a specified pattern. In case of a mismatch, ; Searches a buffer for a specified pattern. In case of a mismatch,
; uses the value of the mismatched byte to skip across as many ; uses the value of the mismatched byte to skip across as many
; potential match locations as possible (partial Boyer-Moore). ; potential match locations as possible (partial Boyer-Moore).
@ -181,6 +182,7 @@ about 60 percent faster than Listing 14.3.
ret ret
_FindString endp _FindString endp
end end
```
Note that Table 14.1 includes the time required to build the skip table Note that Table 14.1 includes the time required to build the skip table
each time **FindString** is called. This time could be eliminated for each time **FindString** is called. This time could be eliminated for

View file

@ -12,6 +12,7 @@ pages: 284-287
**LISTING 15.1 L15-1.C** **LISTING 15.1 L15-1.C**
```c
/* Deletes the node in a linked list that follows the indicated node. /* Deletes the node in a linked list that follows the indicated node.
Assumes list is headed by a dummy node, so no special testing for Assumes list is headed by a dummy node, so no special testing for
the head-of-list pointer is required. Returns the same pointer the head-of-list pointer is required. Returns the same pointer
@ -24,9 +25,11 @@ pages: 284-287
NodeToDeleteAfter->NextNode->NextNode; NodeToDeleteAfter->NextNode->NextNode;
return(NodeToDeleteAfter); return(NodeToDeleteAfter);
} }
```
**LISTING 15.2 LLIST.H** **LISTING 15.2 LLIST.H**
```c
/* Linked list header file. */ /* Linked list header file. */
#define MAX_TEXT_LENGTH 100 /* longest allowed Text field */ #define MAX_TEXT_LENGTH 100 /* longest allowed Text field */
#define SENTINEL 32767 /* largest possible Value field */ #define SENTINEL 32767 /* largest possible Value field */
@ -42,9 +45,11 @@ pages: 284-287
struct LinkNode *InitLinkedList(void); struct LinkNode *InitLinkedList(void);
struct LinkNode *InsertNodeSorted(struct LinkNode *, struct LinkNode *InsertNodeSorted(struct LinkNode *,
struct LinkNode *); struct LinkNode *);
```
**LISTING 15.3 L15-3.C** **LISTING 15.3 L15-3.C**
```c
/* Deletes the node in the specified linked list that follows the /* Deletes the node in the specified linked list that follows the
indicated node. List is headed by a head-of-list pointer; if the indicated node. List is headed by a head-of-list pointer; if the
pointer to the node to delete after points to the head-of-list pointer to the node to delete after points to the head-of-list
@ -63,6 +68,7 @@ pages: 284-287
} }
return(NodeToDeleteAfter); return(NodeToDeleteAfter);
} }
```
However, it is true that if you're going to store a variety of types of However, it is true that if you're going to store a variety of types of
structures in your linked lists, you should start each node with the structures in your linked lists, you should start each node with the
@ -123,6 +129,7 @@ value has to perform two tests in the inner loop, as shown in Listing
**LISTING 15.4 L15-4.C** **LISTING 15.4 L15-4.C**
```c
/* Finds the first node in a linked list with a value field greater /* Finds the first node in a linked list with a value field greater
than or equal to a key value, and returns a pointer to the node than or equal to a key value, and returns a pointer to the node
preceding that node (to facilitate insertion and deletion), or a preceding that node (to facilitate insertion and deletion), or a
@ -145,6 +152,7 @@ value has to perform two tests in the inner loop, as shown in Listing
return(NodePtr); /* success; return pointer to node preceding return(NodePtr); /* success; return pointer to node preceding
node that was >= */ node that was >= */
} }
```
Suppose, however, that we make the tail node a *sentinel* by giving it a Suppose, however, that we make the tail node a *sentinel* by giving it a
value that is guaranteed to terminate the search, as shown in Figure value that is guaranteed to terminate the search, as shown in Figure

View file

@ -12,6 +12,7 @@ pages: 287-290
**LISTING 15.5 L15-5.C** **LISTING 15.5 L15-5.C**
```c
/* Finds the first node in a value-sorted linked list that /* Finds the first node in a value-sorted linked list that
has a Value field greater than or equal to a key value, and has a Value field greater than or equal to a key value, and
returns a pointer to the node preceding that node (to facilitate returns a pointer to the node preceding that node (to facilitate
@ -33,6 +34,7 @@ pages: 287-290
return(NodePtr); /* success; return pointer to node preceding return(NodePtr); /* success; return pointer to node preceding
node that was >= */ node that was >= */
} }
```
![**Figure 15.4**  *List terminated by a sentinel.*](images/15-04.jpg) ![**Figure 15.4**  *List terminated by a sentinel.*](images/15-04.jpg)
@ -80,6 +82,7 @@ before you write a single line of code.
**LISTING 15.6 L15-6.C** **LISTING 15.6 L15-6.C**
```c
/* Suite of functions for maintaining a linked list sorted by /* Suite of functions for maintaining a linked list sorted by
ascending order of the Value field. The list is circular; that ascending order of the Value field. The list is circular; that
is,it has a dummy node as both the head and the tail of the list. is,it has a dummy node as both the head and the tail of the list.
@ -145,3 +148,4 @@ before you write a single line of code.
NodePtr->NextNode = NodeToInsert; NodePtr->NextNode = NodeToInsert;
return(NodePtr); return(NodePtr);
} }
```

View file

@ -12,6 +12,7 @@ pages: 290-293
**LISTING 15.7 L15-7.ASM** **LISTING 15.7 L15-7.ASM**
```nasm
; C near-callable assembly function for inserting a new node in a ; C near-callable assembly function for inserting a new node in a
; linked list sorted by ascending order of the Value field. The list ; linked list sorted by ascending order of the Value field. The list
; is circular; that is, it has a dummy node as both the head and the ; is circular; that is, it has a dummy node as both the head and the
@ -71,9 +72,11 @@ pages: 290-293
ret ret
_InsertNodeSorted endp _InsertNodeSorted endp
end end
```
**LISTING 15.8 L15-8.C** **LISTING 15.8 L15-8.C**
```c
/* Sample linked list program. Tested with Borland C++. */ /* Sample linked list program. Tested with Borland C++. */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
@ -155,6 +158,7 @@ pages: 290-293
} }
} }
} }
```
### Hi/Lo in 24 Bytes {#Heading6} ### Hi/Lo in 24 Bytes {#Heading6}
@ -182,6 +186,7 @@ reads closely enough.
**LISTING 15.9 L15-9.ASM** **LISTING 15.9 L15-9.ASM**
```nasm
; Find the greatest or smallest unsigned int. ; Find the greatest or smallest unsigned int.
; C callable (small model); 24 bytes. ; C callable (small model); 24 bytes.
; By David Stafford. ; By David Stafford.
@ -209,6 +214,7 @@ reads closely enough.
jnz top jnz top
ret ret
```
Before I end this chapter, let me say that I get a lot of feedback from Before I end this chapter, let me say that I get a lot of feedback from
my readers, and it's much appreciated. Keep those cards, letters, and my readers, and it's much appreciated. Keep those cards, letters, and

View file

@ -98,6 +98,7 @@ timed from a RAM disk on a 20 MHz 386.
**LISTING 16.1 L16-1.C** **LISTING 16.1 L16-1.C**
```c
/* Word-counting program. Tested with Borland C++ in C /* Word-counting program. Tested with Borland C++ in C
compilation mode and the small model. */ compilation mode and the small model. */
@ -170,3 +171,4 @@ timed from a RAM disk on a 20 MHz 386.
printf("\nTotal words in file: %lu\n", WordCount); printf("\nTotal words in file: %lu\n", WordCount);
return(0); return(0);
} }
```

View file

@ -22,6 +22,7 @@ generates.
**LISTING 16.2 L16-2.C** **LISTING 16.2 L16-2.C**
```c
/* Word-counting program incorporating assembly language. Tested /* Word-counting program incorporating assembly language. Tested
with Borland C++ in C compilation mode & the small model. */ with Borland C++ in C compilation mode & the small model. */
@ -80,9 +81,11 @@ generates.
printf("\nTotal words in file: %lu\n", WordCount); printf("\nTotal words in file: %lu\n", WordCount);
return(0); return(0);
} }
```
**LISTING 16.3 L16-3.ASM** **LISTING 16.3 L16-3.ASM**
```nasm
; Assembly subroutine for Listing 16.2. Scans through Buffer, of ; Assembly subroutine for Listing 16.2. Scans through Buffer, of
; length BufferLength, counting words and updating WordCount as ; length BufferLength, counting words and updating WordCount as
; appropriate. BufferLength must be > 0. *CharFlag and *WordCount ; appropriate. BufferLength must be > 0. *CharFlag and *WordCount
@ -163,6 +166,7 @@ generates.
ret ret
_ScanBuffer endp _ScanBuffer endp
end end
```
#### Which Way to Go from Here? {#Heading4} #### Which Way to Go from Here? {#Heading4}

View file

@ -12,6 +12,7 @@ pages: 303-305
**LISTING 16.4 L16-4.ASM** **LISTING 16.4 L16-4.ASM**
```nasm
; Assembly subroutine for Listing 16.2. Scans through Buffer, of ; Assembly subroutine for Listing 16.2. Scans through Buffer, of
; length BufferLength, counting words and updating WordCount as ; length BufferLength, counting words and updating WordCount as
; appropriate, using a lookup table-based approach. BufferLength ; appropriate, using a lookup table-based approach. BufferLength
@ -102,7 +103,7 @@ pages: 303-305
jmp Done jmp Done
_ScanBuffer endp _ScanBuffer endp
end end
```
Listing 16.4 features several interesting tricks. First, it uses Listing 16.4 features several interesting tricks. First, it uses
**LODSB** and **XLAT** in succession, a very neat way to get a **LODSB** and **XLAT** in succession, a very neat way to get a

View file

@ -40,9 +40,9 @@ over Terje's 386 native-mode code, and found the critical inner loop,
which was indeed as tight as one could imagine, consisting of just a few which was indeed as tight as one could imagine, consisting of just a few
386 native-mode instructions. However, one of the instructions was this: 386 native-mode instructions. However, one of the instructions was this:
```nasm
CMP DH,[EBX+EAX] CMP DH,[EBX+EAX]
```
Harmless enough, save for two things. First, EBX happened to be zero at Harmless enough, save for two things. First, EBX happened to be zero at
this point (a leftover from an earlier version of the code, as it turned this point (a leftover from an earlier version of the code, as it turned

View file

@ -36,6 +36,7 @@ Table: Table 16.2 The top four word-counting entries.
**LISTING 16.5 QSCAN3.ASM** **LISTING 16.5 QSCAN3.ASM**
```nasm
; QSCAN3.ASM ; QSCAN3.ASM
; David Stafford ; David Stafford
@ -214,3 +215,4 @@ Table: Table 16.2 The top four word-counting entries.
.fardata WordTable .fardata WordTable
include qscan3.inc ;built by MAKETAB include qscan3.inc ;built by MAKETAB
end end
```

View file

@ -12,6 +12,7 @@ pages: 313-316
**Listing 16.6 OPT2.ASM** **Listing 16.6 OPT2.ASM**
```nasm
; ;
; Opt2 Final optimization word count ; Opt2 Final optimization word count
; Written by Michael Abrash ; Written by Michael Abrash
@ -116,6 +117,7 @@ pages: 313-316
ret ret
_ScanBuffer endp _ScanBuffer endp
end end
```
### Level 2: A New Perspective {#Heading11} ### Level 2: A New Perspective {#Heading11}

View file

@ -12,6 +12,7 @@ pages: 316-319
**Listing 16.7 L16-7.ASM** **Listing 16.7 L16-7.ASM**
```nasm
ScanLoop: ScanLoop:
lodsw ;get the next 2 bytes (AL = first, AH = 2nd) lodsw ;get the next 2 bytes (AL = first, AH = 2nd)
xlat ;look up first's char/not status xlat ;look up first's char/not status
@ -25,7 +26,7 @@ pages: 316-319
mov dl,al mov dl,al
dec dx dec dx
jnz ScanLoop jnz ScanLoop
```
John later divides the transition count by two to get the word count. 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 (Food for thought: It's also possible to use **CMP** and **ADC** to
@ -66,6 +67,7 @@ report a "location counter overflow" warning; ignore it.)
**LISTING 16.8 MAKETAB.C** **LISTING 16.8 MAKETAB.C**
```c
// MAKETAB.C — Build QSCAN3.INC for QSCAN3.ASM // MAKETAB.C — Build QSCAN3.INC for QSCAN3.ASM
#include <stdio.h> #include <stdio.h>
@ -98,7 +100,7 @@ report a "location counter overflow" warning; ignore it.)
fclose( t ); fclose( t );
} }
```
David's approach is simplicity itself, although his implementation David's approach is simplicity itself, although his implementation
arguably is not. Consider any three sequential bytes in the buffer. arguably is not. Consider any three sequential bytes in the buffer.

View file

@ -12,6 +12,7 @@ pages: 325-329
**LISTING 17.1 L17-1.CPP** **LISTING 17.1 L17-1.CPP**
```cpp
/* C++ Game of Life implementation for any mode for which mode set /* C++ Game of Life implementation for any mode for which mode set
and draw pixel functions can be provided. and draw pixel functions can be provided.
Tested with Borland C++ in the small model. */ Tested with Borland C++ in the small model. */
@ -206,9 +207,11 @@ pages: 325-329
} }
} }
} }
```
**LISTING 17.2 L17-2.CPP** **LISTING 17.2 L17-2.CPP**
```cpp
/* VGA mode 13h functions for Game of Life. /* VGA mode 13h functions for Game of Life.
Tested with Borland C++. */ Tested with Borland C++. */
#include <stdio.h> #include <stdio.h>
@ -265,3 +268,4 @@ pages: 325-329
gotoxy(TEXT_X_OFFSET + x, y); gotoxy(TEXT_X_OFFSET + x, y);
puts(text); puts(text);
} }
```

View file

@ -48,6 +48,7 @@ improvement.
**LISTING 17.3 L17-3.CPP** **LISTING 17.3 L17-3.CPP**
```cpp
/* cellmap class definition, constructor, copy_cells(), set_cell(), /* cellmap class definition, constructor, copy_cells(), set_cell(),
clear_cell(), cell_state(), count_neighbors(), and clear_cell(), cell_state(), count_neighbors(), and
next_generation() for fast, hard-wired neighbor count approach. next_generation() for fast, hard-wired neighbor count approach.
@ -209,3 +210,4 @@ improvement.
} }
} }
} }
```

View file

@ -51,6 +51,7 @@ Listing 17.3.
**LISTING 17.4 L17-4.CPP** **LISTING 17.4 L17-4.CPP**
```cpp
/* next_generation(), implemented using fast, all-in-one hard-wired /* next_generation(), implemented using fast, all-in-one hard-wired
neighbor count/update/draw function. Otherwise, the same as neighbor count/update/draw function. Otherwise, the same as
Listing 17.3. */ Listing 17.3. */
@ -131,6 +132,7 @@ Listing 17.3.
row_cell_ptr += width_in_bytes; // point to start of next row row_cell_ptr += width_in_bytes; // point to start of next row
} }
} }
```
Listing 17.4 and Listing 17.3 are functionally the same; the only Listing 17.4 and Listing 17.3 are functionally the same; the only
difference lies in how **next\_generation()** is implemented. (Only difference lies in how **next\_generation()** is implemented. (Only

View file

@ -12,6 +12,7 @@ pages: 340-345
**LISTING 17.5 L17-5.CPP** **LISTING 17.5 L17-5.CPP**
```cpp
/* C++ Game of Life implementation for any mode for which mode set /* C++ Game of Life implementation for any mode for which mode set
and draw pixel functions can be provided. The cellmap stores the and draw pixel functions can be provided. The cellmap stores the
neighbor count for each cell as well as the state of each cell; neighbor count for each cell as well as the state of each cell;
@ -285,3 +286,4 @@ pages: 340-345
} }
} while (—init_length); } while (—init_length);
} }
```

View file

@ -12,13 +12,16 @@ pages: 352-361
**LISTING 18.1 BUILD.BAT** **LISTING 18.1 BUILD.BAT**
```bat
bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 lcomp.c bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 lcomp.c
lcomp > qlife.asm lcomp > qlife.asm
tasmx /mx /kh30000 qlife tasmx /mx /kh30000 qlife
bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 qlife.obj main.c video.c bcc -v -D%1=%2;%2=%3;%3=%4;%4=%5;%5=%6;%6=%7;%7=%8;%8 qlife.obj main.c video.c
```
**LISTING 18.2 LCOMP.C** **LISTING 18.2 LCOMP.C**
```c
// LCOMP.C // LCOMP.C
// //
// Life compiler, ver 1.3 // Life compiler, ver 1.3
@ -512,3 +515,4 @@ pages: 352-361
printf( "LIFE ends\nend\n" ); printf( "LIFE ends\nend\n" );
} }
```

View file

@ -12,6 +12,7 @@ pages: 361-365
**LISTING 18.3 MAIN.C** **LISTING 18.3 MAIN.C**
```c
// MAIN.C // MAIN.C
// //
// David Stafford // David Stafford
@ -102,9 +103,11 @@ pages: 361-365
printf( "Time: %f generations/second\n", printf( "Time: %f generations/second\n",
(double)generation / (double)end_time * 18.2 ); (double)generation / (double)end_time * 18.2 );
} }
```
**LISTING 18.4 VIDEO.C** **LISTING 18.4 VIDEO.C**
```c
/* VGA mode 13h functions for Game of Life. /* VGA mode 13h functions for Game of Life.
Tested with Borland C++. */ Tested with Borland C++. */
#include <stdio.h> #include <stdio.h>
@ -141,9 +144,11 @@ pages: 361-365
gotoxy(TEXT_X_OFFSET + x, y); gotoxy(TEXT_X_OFFSET + x, y);
puts(text); puts(text);
} }
```
**LISTING 18.5 LIFE.H** **LISTING 18.5 LIFE.H**
```c
void far NextGen( void ); void far NextGen( void );
extern unsigned short CellMap[]; extern unsigned short CellMap[];
@ -161,6 +166,7 @@ pages: 361-365
#define WRAPRIGHT (LEFT * (WIDTH - 1)) #define WRAPRIGHT (LEFT * (WIDTH - 1))
#define WRAPUP (DOWN * (HEIGHT - 1)) #define WRAPUP (DOWN * (HEIGHT - 1))
#define WRAPDOWN (UP * (HEIGHT - 1)) #define WRAPDOWN (UP * (HEIGHT - 1))
```
### Keeping Track of Change with a Change List {#Heading5} ### Keeping Track of Change with a Change List {#Heading5}

View file

@ -103,9 +103,11 @@ and branching to the routine at that address. As with everything in this
amazing program, this represents the least possible work to accomplish amazing program, this represents the least possible work to accomplish
the desired result—just three instructions: the desired result—just three instructions:
```nasm
mov dh,[bp+1] mov dh,[bp+1]
or dh,1 or dh,1
jmp dx jmp dx
```
These suffice to select the proper, minimum-work code to process the These suffice to select the proper, minimum-work code to process the
next cell triplet that has changed, and all potentially affected next cell triplet that has changed, and all potentially affected

View file

@ -102,6 +102,7 @@ pipelines mean that an AGI can now slow down execution of an instruction
that's *three* instructions away from the AGI (because four instructions that's *three* instructions away from the AGI (because four instructions
can execute in two cycles). So, for example, the code sequence can execute in two cycles). So, for example, the code sequence
```nasm
add edx,4 ;U-pipe cycle 1 add edx,4 ;U-pipe cycle 1
mov ecx,[ebx] ;V-pipe cycle 1 mov ecx,[ebx] ;V-pipe cycle 1
add ebx,4 ;U-pipe cycle 2 add ebx,4 ;U-pipe cycle 2
@ -109,6 +110,7 @@ can execute in two cycles). So, for example, the code sequence
; due to AGI ; due to AGI
; (would have been ; (would have been
; V-pipe cycle 2) ; V-pipe cycle 2)
```
takes three cycles rather than the two cycles it should take, because takes three cycles rather than the two cycles it should take, because
EDX was modified on cycle 1 and an attempt was made to use it on cycle EDX was modified on cycle 1 and an attempt was made to use it on cycle
@ -116,10 +118,12 @@ two, before the AGI had time to clear—even though there are two
instructions between the instructions that are actually involved in the instructions between the instructions that are actually involved in the
AGI. Rearranging the code like AGI. Rearranging the code like
```nasm
mov ecx,[ebx] ;U-pipe cycle 1 mov ecx,[ebx] ;U-pipe cycle 1
add ebx,4 ;V-pipe cycle 1 add ebx,4 ;V-pipe cycle 1
mov [edx+4],ecx ;U-pipe cycle 2 mov [edx+4],ecx ;U-pipe cycle 2
add edx,4 ;V-pipe cycle 2 add edx,4 ;V-pipe cycle 2
```
makes it functionally identical, but cuts the cycles to 2—a 50 percent makes it functionally identical, but cuts the cycles to 2—a 50 percent
improvement. Clearly, avoiding AGIs becomes a much more challenging and improvement. Clearly, avoiding AGIs becomes a much more challenging and

View file

@ -38,9 +38,11 @@ word-aligned, dword operands should be dword-aligned, and qword operands
(double-precision variables) should be qword-aligned. Spanning a dword (double-precision variables) should be qword-aligned. Spanning a dword
boundary, as in boundary, as in
```nasm
mov ebx,3 mov ebx,3
: :
mov eax,[ebx] mov eax,[ebx]
```
costs three cycles. On the other hand, as noted above, branch targets costs three cycles. On the other hand, as noted above, branch targets
can now span cache lines with impunity, so on the Pentium there's no can now span cache lines with impunity, so on the Pentium there's no

View file

@ -40,6 +40,7 @@ waits until the first instruction is done, then executes in the U-pipe,
possibly pairing with the next instruction in line if all pairing possibly pairing with the next instruction in line if all pairing
conditions are met. conditions are met.
```nasm
MOV reg,reg (1 cycle) MOV reg,reg (1 cycle)
mem,reg (1 cycle) mem,reg (1 cycle)
reg,mem (1 cycle) reg,mem (1 cycle)
@ -75,6 +76,7 @@ conditions are met.
JMP/CALL near (1 cycle if predicted correctly; JMP/CALL near (1 cycle if predicted correctly;
3 cycles otherwise) 3 cycles otherwise)
```
† Can't execute in V-pipe if address contains a displacement † Can't execute in V-pipe if address contains a displacement
@ -100,6 +102,7 @@ each of 2 cycles, for 2\*0.5 = 1 cycle total execution time), as shown
in Figure 20.3—a full cycle *faster* than **PUSH [*mem*]**, which takes in Figure 20.3—a full cycle *faster* than **PUSH [*mem*]**, which takes
2 cycles. 2 cycles.
```nasm
MOV reg,reg (1 cycle) MOV reg,reg (1 cycle)
mem,reg (1 cycle) mem,reg (1 cycle)
reg,mem (1 cycle) reg,mem (1 cycle)
@ -132,8 +135,9 @@ in Figure 20.3—a full cycle *faster* than **PUSH [*mem*]**, which takes
SHL/SHR/SAL/SAR reg,immediate (1 cycle)†† SHL/SHR/SAL/SAR reg,immediate (1 cycle)††
ROL/ROR/RCL/RCR reg,1 (1 cycle) ROL/ROR/RCL/RCR reg,1 (1 cycle)
```
† Can't pair if address contains a displacement † Can't pair if address contains a displacement\
†† Includes shift-by-1 forms of instructions †† Includes shift-by-1 forms of instructions
**Table 20.2 Instructions that, when executed in the U-pipe, allow **Table 20.2 Instructions that, when executed in the U-pipe, allow
@ -155,25 +159,33 @@ One downside of this "RISCification" (turning complex instructions into
simple, RISC-like ones) of Pentium-optimized code is that it makes for simple, RISC-like ones) of Pentium-optimized code is that it makes for
substantially larger code. For example, substantially larger code. For example,
```nasm
push dword ptr [esi] push dword ptr [esi]
```
is one byte smaller than this sequence: is one byte smaller than this sequence:
```nasm
mov eax,[esi] mov eax,[esi]
push eax push eax
```
![**Figure 20.3**  *Pushing a value from memory effectively in one ![**Figure 20.3**  *Pushing a value from memory effectively in one
cycle.*](images/20-03.jpg) cycle.*](images/20-03.jpg)
A more telling example is the following A more telling example is the following
```nasm
add [MemVar],eax add [MemVar],eax
```
versus the equivalent: versus the equivalent:
```nasm
mov edx,[MemVar] mov edx,[MemVar]
add edx,eax add edx,eax
mov [MemVar],edx mov [MemVar],edx
```
The single complex instruction takes 3 cycles and is 6 bytes long; with The single complex instruction takes 3 cycles and is 6 bytes long; with
proper sequencing, interleaving the simple instructions with other proper sequencing, interleaving the simple instructions with other

View file

@ -79,23 +79,29 @@ the same values for address
bits 2, 3, and 4 (fall in the same bank) in tight loops, and you should bits 2, 3, and 4 (fall in the same bank) in tight loops, and you should
also avoid sequences like also avoid sequences like
```nasm
mov bl,[esi] mov bl,[esi]
mov bh,[esi+1] mov bh,[esi+1]
```
because both operands will generally be in the same bank. An alternative because both operands will generally be in the same bank. An alternative
is to place another instruction between the two instructions that access is to place another instruction between the two instructions that access
the same bank, as in this sequence: the same bank, as in this sequence:
```nasm
mov bl,[esi] mov bl,[esi]
mov edi,edx mov edi,edx
mov bh,[esi+1] mov bh,[esi+1]
```
By the way, the reason a code sequence that takes two instructions to By the way, the reason a code sequence that takes two instructions to
load a single word is attractive in a 32-bit segment is because it takes load a single word is attractive in a 32-bit segment is because it takes
only one cycle when the two instructions can be paired with other only one cycle when the two instructions can be paired with other
instructions; by contrast, the obvious way of loading BX instructions; by contrast, the obvious way of loading BX
```nasm
mov bx,[esi] mov bx,[esi]
```
takes 1.5 to two cycles because the size prefix can't pair, as described takes 1.5 to two cycles because the size prefix can't pair, as described
below. This is yet another example of how different Pentium optimization below. This is yet another example of how different Pentium optimization
@ -116,5 +122,7 @@ but that turns out to not always be the case. Two two-cycle instructions
execute in two cycles, so it's okay to pair two instructions such as execute in two cycles, so it's okay to pair two instructions such as
these: these:
```nasm
add esi,[SourceSkip] ;U-pipe cycles 1 and 2 add esi,[SourceSkip] ;U-pipe cycles 1 and 2
add edi,[DestinationSkip] ;V-pipe cycles 1 and 2 add edi,[DestinationSkip] ;V-pipe cycles 1 and 2
```

View file

@ -41,13 +41,17 @@ it's the only way to get both pipes running at capacity.
You may well ask why it's necessary to interleave operations, as is done You may well ask why it's necessary to interleave operations, as is done
in Figure 20.7. It seems simpler just to turn in Figure 20.7. It seems simpler just to turn
```nasm
and [ebx],al and [ebx],al
```
into into
```nasm
mov dl,[ebx] mov dl,[ebx]
and dl,al and dl,al
mov [ebx],dl mov [ebx],dl
```
and be done with it. The problem here is one of dependency. Before the and be done with it. The problem here is one of dependency. Before the
Pentium can execute **AND DL,AL,**, it must first know what is in DL, Pentium can execute **AND DL,AL,**, it must first know what is in DL,

View file

@ -65,7 +65,9 @@ areas of AGIs and register dependencies.
AGIs are *Address Generation Interlocks*, a fancy way of saying that if AGIs are *Address Generation Interlocks*, a fancy way of saying that if
a register is used to address memory, as is EBX in this instruction a register is used to address memory, as is EBX in this instruction
```nasm
mov [ebx],eax mov [ebx],eax
```
and the value of the register is not set far enough ahead for the and the value of the register is not set far enough ahead for the
Pentium to perform the addressing calculations before the instruction Pentium to perform the addressing calculations before the instruction
@ -101,6 +103,7 @@ As an example of a sort of AGI that's new to the Pentium, consider the
following test for a NULL pointer, followed by the use of the pointer if following test for a NULL pointer, followed by the use of the pointer if
it's not NULL: it's not NULL:
```nasm
push ebx ;U-pipe cycle 1 push ebx ;U-pipe cycle 1
mov ebx,[Ptr] ;V-pipe cycle 1 mov ebx,[Ptr] ;V-pipe cycle 1
and ebx,ebx ;U-pipe cycle 2 and ebx,ebx ;U-pipe cycle 2
@ -109,6 +112,7 @@ it's not NULL:
mov edx,[ebp-8] ;V-pipe cycle 3 lockstep idle mov edx,[ebp-8] ;V-pipe cycle 3 lockstep idle
;U-pipe cycle 4 mov eax,[ebx] ;U-pipe cycle 4 mov eax,[ebx]
;V-pipe cycle 4 mov edx,[ebp-8] ;V-pipe cycle 4 mov edx,[ebp-8]
```
This commonplace code loses a U-pipe cycle to the AGI caused by **AND This commonplace code loses a U-pipe cycle to the AGI caused by **AND
EBX,EBX**, followed by the attempt two instructions later to use EBX to EBX,EBX**, followed by the attempt two instructions later to use EBX to

View file

@ -15,16 +15,22 @@ stack pointer. Implicit modifiers of ESP, such as **PUSH** and **POP**,
are special-cased so you don't have to worry about AGIs. However, if you are special-cased so you don't have to worry about AGIs. However, if you
explicitly modify ESP with this instruction explicitly modify ESP with this instruction
```nasm
sub esp,100h sub esp,100h
```
for example, or with the popular for example, or with the popular
```nasm
mov esp,ebp mov esp,ebp
```
you can then get AGIs if you attempt to use ESP to address memory, you can then get AGIs if you attempt to use ESP to address memory,
either explicitly with instructions like this one either explicitly with instructions like this one
```nasm
moveax,[esp+20h] moveax,[esp+20h]
```
or via **PUSH**, **POP**, or other instructions that implicitly use ESP or via **PUSH**, **POP**, or other instructions that implicitly use ESP
as an addressing register. as an addressing register.
@ -32,25 +38,31 @@ as an addressing register.
On the 486, any instruction that had both a constant value and an On the 486, any instruction that had both a constant value and an
addressing displacement, such as addressing displacement, such as
```nasm
mov dword ptr [ebp+16],1 mov dword ptr [ebp+16],1
```
suffered a 1-cycle penalty, taking a total of 2 cycles. Such suffered a 1-cycle penalty, taking a total of 2 cycles. Such
instructions take only one cycle on the Pentium, but they cannot pair, instructions take only one cycle on the Pentium, but they cannot pair,
so they're still the most expensive sort of **MOV**. Knowing this can so they're still the most expensive sort of **MOV**. Knowing this can
speed up something as simple as zeroing two memory variables, as in speed up something as simple as zeroing two memory variables, as in
```nasm
sub eax,eax ;U-pipe 1 sub eax,eax ;U-pipe 1
;any V-pipe pairable ;any V-pipe pairable
; instruction can go here, ; instruction can go here,
; or SUB could be in V-pipe ; or SUB could be in V-pipe
mov [MemVar1],eax ;U-pipe 2 mov [MemVar1],eax ;U-pipe 2
mov [MemVar2],eax ;V-pipe 2 mov [MemVar2],eax ;V-pipe 2
```
which should never be slower and should potentially be 0.5 cycles which should never be slower and should potentially be 0.5 cycles
faster, and six bytes smaller than this sequence: faster, and six bytes smaller than this sequence:
```nasm
mov [MemVar1],0 ;U-pipe 1 mov [MemVar1],0 ;U-pipe 1
mov [MemVar2],0 ;U-pipe 2 mov [MemVar2],0 ;U-pipe 2
```
Note, however, that my experiments thus far indicate that the two writes Note, however, that my experiments thus far indicate that the two writes
in the first case don't actually pair (possibly because the memory in the first case don't actually pair (possibly because the memory
@ -66,10 +78,12 @@ optimization: register contention. The basic premise here is simple: You
can't use the same register in two inherently sequential ways in a can't use the same register in two inherently sequential ways in a
single cycle. For example, you can't execute single cycle. For example, you can't execute
```nasm
inc eax ;U-pipe cycle 1 inc eax ;U-pipe cycle 1
;V-pipe idle cycle 1 ;V-pipe idle cycle 1
; due to dependency ; due to dependency
and ebx,eax ;U-pipe cycle 2 and ebx,eax ;U-pipe cycle 2
```
in a single cycle; **AND EBX,EAX** can't execute until the value in EAX in a single cycle; **AND EBX,EAX** can't execute until the value in EAX
is known, and that can't happen until **INC EAX** is done. Consequently, is known, and that can't happen until **INC EAX** is done. Consequently,
@ -86,10 +100,12 @@ to write to the same register on the same cycle. While that may not seem
like a particularly useful operation in general, it can happen when like a particularly useful operation in general, it can happen when
subregisters are being set, as in the following subregisters are being set, as in the following
```nasm
sub eax,eax ;U-pipe cycle 1 sub eax,eax ;U-pipe cycle 1
;V-pipe idle cycle 1 ;V-pipe idle cycle 1
; due to register contention ; due to register contention
mov al,[Var] ;U-pipe cycle 2 mov al,[Var] ;U-pipe cycle 2
```
where an attempt is made to set both EAX and its AL subregister on the where an attempt is made to set both EAX and its AL subregister on the
same cycle. Write-after-write contention implies that the two same cycle. Write-after-write contention implies that the two
@ -103,8 +119,10 @@ Intel has special-cased some very useful exceptions to register
contention. Happily, write-after-read operations do *not* cause contention. Happily, write-after-read operations do *not* cause
contention. Such operations, as in contention. Such operations, as in
```nasm
mov eax,edx ;U-pipe cycle 1 mov eax,edx ;U-pipe cycle 1
sub edx,edxX ;V-pipe cycle 1 sub edx,edxX ;V-pipe cycle 1
```
are free of charge. are free of charge.
@ -117,12 +135,14 @@ register contention on ESP—but not AGIs—with instructions that use ESP
explicitly, such as **MOV EAX,[ESP+4]**.) Without this special case, the explicitly, such as **MOV EAX,[ESP+4]**.) Without this special case, the
following sequence would hardly use the V-pipe at all: following sequence would hardly use the V-pipe at all:
```nasm
mov eax,[MemVar] ;U-pipe cycle 1 mov eax,[MemVar] ;U-pipe cycle 1
push esi ;V-pipe cycle 1 push esi ;V-pipe cycle 1
push eax ;U-pipe cycle 2 push eax ;U-pipe cycle 2
push edi ;V-pipe cycle 2 push edi ;V-pipe cycle 2
push ebx ;U-pipe cycle 3 push ebx ;U-pipe cycle 3
call FooTilde ;V-pipe cycle 3 call FooTilde ;V-pipe cycle 3
```
But in fact, all the instructions pair, even though ESP is modified five But in fact, all the instructions pair, even though ESP is modified five
times in the space of six instructions. times in the space of six instructions.
@ -133,11 +153,13 @@ pair only in the V-pipe: branches. Any near call or conditional or
unconditional near jump can execute in the V-pipe paired with any unconditional near jump can execute in the V-pipe paired with any
pairable U-pipe instruction, as illustrated by this sequence: pairable U-pipe instruction, as illustrated by this sequence:
```nasm
LoopTop: LoopTop:
mov [esi],eax ;U-pipe cycle 1 mov [esi],eax ;U-pipe cycle 1
add esi,4 ;V-pipe cycle 1 add esi,4 ;V-pipe cycle 1
dec ecx ;U-pipe cycle 2 dec ecx ;U-pipe cycle 2
jnz LoopTop ;V-pipe cycle 2 jnz LoopTop ;V-pipe cycle 2
```
Branches can't pair in the U-pipe; a branch that executes in the U-pipe Branches can't pair in the U-pipe; a branch that executes in the U-pipe
runs alone, with the V-pipe idle. If a call or jump is correctly runs alone, with the V-pipe idle. If a call or jump is correctly
@ -159,6 +181,7 @@ instruction, the other instructions will go through different pipes than
previously, and cause the loop as a whole to take 50 percent longer, previously, and cause the loop as a whole to take 50 percent longer,
even though we only added 25 percent more cycles: even though we only added 25 percent more cycles:
```nasm
LoopTop: LoopTop:
inc edx ;U-pipe cycle 1 inc edx ;U-pipe cycle 1
mov [esi],eax ;V-pipe cycle 1 mov [esi],eax ;V-pipe cycle 1
@ -168,3 +191,4 @@ even though we only added 25 percent more cycles:
;V-pipe idle cycle 3 ;V-pipe idle cycle 3
; because JNZ can't ; because JNZ can't
; pair in the U-pipe ; pair in the U-pipe
```

View file

@ -40,6 +40,7 @@ right?
**LISTING 21.1 L21-1.ASM** **LISTING 21.1 L21-1.ASM**
```nasm
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; starting at ESI, of length ECX words. ; starting at ESI, of length ECX words.
; Returns checksum in AX. ; Returns checksum in AX.
@ -68,6 +69,7 @@ right?
add esi,2 ;cycle 5 V-pipe add esi,2 ;cycle 5 V-pipe
dec ecx ;cycle 6 U-pipe dec ecx ;cycle 6 U-pipe
jnz ckloop ;cycle 6 V-pipe jnz ckloop ;cycle 6 V-pipe
```
Wrong, wrong, wrong! As detailed in Listing 21.1, this loop should take Wrong, wrong, wrong! As detailed in Listing 21.1, this loop should take
6 cycles per checksummed word in 32-bit protected mode, a ridiculously 6 cycles per checksummed word in 32-bit protected mode, a ridiculously

View file

@ -27,6 +27,7 @@ same cache data bank, as discussed in the last chapter).
**LISTING 21.2 L21-2.ASM** **LISTING 21.2 L21-2.ASM**
```nasm
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; starting at ESI, of length ECX words. ; starting at ESI, of length ECX words.
; Returns checksum in AX. ; Returns checksum in AX.
@ -53,6 +54,7 @@ same cache data bank, as discussed in the last chapter).
ckloopend: ckloopend:
add ax,dx ;checksum the last word add ax,dx ;checksum the last word
adc eax,0 adc eax,0
```
Listing 21.3 is a more sophisticated attempt to speed up the checksum Listing 21.3 is a more sophisticated attempt to speed up the checksum
calculation. Here we see a hallmark of Pentium optimization: two calculation. Here we see a hallmark of Pentium optimization: two
@ -68,6 +70,7 @@ placement of **ADD ESI,4** to avoid an AGI.
**LISTING 21.3 L21-3.ASM** **LISTING 21.3 L21-3.ASM**
```nasm
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; starting at ESI, of length ECX words. ; starting at ESI, of length ECX words.
; Returns checksum in AX. ; Returns checksum in AX.
@ -114,6 +117,7 @@ placement of **ADD ESI,4** to avoid an AGI.
add ax,dx add ax,dx
adc eax,0 adc eax,0
ckloopdone: ckloopdone:
```
The checksum loop in Listing 21.3 takes longer than the loop in Listing The checksum loop in Listing 21.3 takes longer than the loop in Listing
21.2, at 6 cycles versus 4 cycles for Listing 21.2—but Listing 21.3 does 21.2, at 6 cycles versus 4 cycles for Listing 21.2—but Listing 21.3 does

View file

@ -12,6 +12,7 @@ pages: 409-411
**LISTING 21.4 L21-4.ASM** **LISTING 21.4 L21-4.ASM**
```nasm
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; starting at ESI, of length ECX words. ; starting at ESI, of length ECX words.
; Returns checksum in AX. ; Returns checksum in AX.
@ -42,6 +43,7 @@ pages: 409-411
shr edx,16 ; into a 16-bit checksum shr edx,16 ; into a 16-bit checksum
add ax,dx add ax,dx
adc eax,0 adc eax,0
```
Listing 21.5 improves upon Listing 21.4 by processing 2 dwords per loop, Listing 21.5 improves upon Listing 21.4 by processing 2 dwords per loop,
thereby bringing the time per checksummed word down to exactly 1 cycle. thereby bringing the time per checksummed word down to exactly 1 cycle.
@ -53,6 +55,7 @@ more registers.
**LISTING 21.5 L21-5.ASM** **LISTING 21.5 L21-5.ASM**
```nasm
; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer ; Calculates TCP/IP (16-bit carry-wrapping) checksum for buffer
; starting at ESI, of length ECX words. ; starting at ESI, of length ECX words.
; Returns checksum in AX. ; Returns checksum in AX.
@ -93,6 +96,7 @@ more registers.
shr edx,16 ; into a 16-bit checksum shr edx,16 ; into a 16-bit checksum
add ax,dx add ax,dx
adc eax,0 adc eax,0
```
Listing 21.5 is undeniably intricate code, and not the sort of thing one Listing 21.5 is undeniably intricate code, and not the sort of thing one
would choose to write as a matter of course. On the other hand, it's would choose to write as a matter of course. On the other hand, it's

View file

@ -55,6 +55,7 @@ are mine.
**LISTING 22.1 L22-1.ASM** **LISTING 22.1 L22-1.ASM**
```nasm
OnStack struc ;data that's stored on the stack after PUSH BP OnStack struc ;data that's stored on the stack after PUSH BP
OldBP dw ? ;caller's BP OldBP dw ? ;caller's BP
RetAddr dw ? ;return address RetAddr dw ? ;return address
@ -89,6 +90,7 @@ are mine.
pop bp ; and caller's BP pop bp ; and caller's BP
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp ClearS endp
```
The first thing you'll notice about Listing 22.1 is that **ClearS** uses The first thing you'll notice about Listing 22.1 is that **ClearS** uses
a **REP STOSW** instruction. That means that we're not going to improve a **REP STOSW** instruction. That means that we're not going to improve

View file

@ -12,6 +12,7 @@ pages: 417-418
**LISTING 22.2 L22-2.ASM** **LISTING 22.2 L22-2.ASM**
```nasm
ClearS proc near ClearS proc near
push bp ;save caller's BP push bp ;save caller's BP
mov bp,sp ;point to stack frame mov bp,sp ;point to stack frame
@ -33,6 +34,7 @@ pages: 417-418
pop bp ;restore caller's BP pop bp ;restore caller's BP
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp ClearS endp
```
(The **OnStack** structure definition doesn't change in any of our (The **OnStack** structure definition doesn't change in any of our
examples, so I'm not going clutter up this chapter by reproducing it for examples, so I'm not going clutter up this chapter by reproducing it for
@ -47,6 +49,7 @@ loading ES and DI as shown in Listing 22.3.
**LISTING 22.3 L22-3.ASM** **LISTING 22.3 L22-3.ASM**
```nasm
ClearS proc near ClearS proc near
push bp ;save caller's BP push bp ;save caller's BP
mov bp,sp ;point to stack frame mov bp,sp ;point to stack frame
@ -68,6 +71,7 @@ loading ES and DI as shown in Listing 22.3.
pop bp ;restore caller's BP pop bp ;restore caller's BP
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp ClearS endp
```
That's good for another three bytes. We're down to 43 bytes, and That's good for another three bytes. We're down to 43 bytes, and
counting. counting.

View file

@ -12,6 +12,7 @@ pages: 419-420
**LISTING 22.5 L22-5.ASM** **LISTING 22.5 L22-5.ASM**
```nasm
ClearS proc near ClearS proc near
push bp ;save caller's BP push bp ;save caller's BP
mov bp,sp ;point to stack frame mov bp,sp ;point to stack frame
@ -29,6 +30,7 @@ pages: 419-420
pop bp ;restore caller's BP pop bp ;restore caller's BP
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp ClearS endp
```
(We could get rid of yet another instruction by having the calling code (We could get rid of yet another instruction by having the calling code
pack both the attribute and the fill value into the same word, but pack both the attribute and the fill value into the same word, but
@ -44,6 +46,7 @@ shown in Listing 22.6.
**LISTING 22.6 L22-6.ASM** **LISTING 22.6 L22-6.ASM**
```nasm
ClearS proc near ClearS proc near
push bp ;save caller's BP push bp ;save caller's BP
mov bp,sp ;point to stack frame mov bp,sp ;point to stack frame
@ -60,6 +63,7 @@ shown in Listing 22.6.
pop bp ;restore caller's BP pop bp ;restore caller's BP
ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack
ClearS endp ClearS endp
```
Well. Now we're down to 28 bytes, having reduced the size of this Well. Now we're down to 28 bytes, having reduced the size of this
subroutine by nearly 50 percent. Only 13 instructions remain. subroutine by nearly 50 percent. Only 13 instructions remain.
@ -103,6 +107,7 @@ With that problem dealt with, Listing 22.7 shows the Zenned version of
**LISTING 22.7 L22-7.ASM** **LISTING 22.7 L22-7.ASM**
```nasm
ClearS procnear ClearS procnear
pop dx ;get the return address pop dx ;get the return address
pop ax ;put fill char into AL pop ax ;put fill char into AL
@ -119,6 +124,7 @@ With that problem dealt with, Listing 22.7 shows the Zenned version of
Bye: Bye:
jmp dx ;return to the calling code jmp dx ;return to the calling code
ClearS endp ClearS endp
```
At long last, we're down to the bare metal. This version of **ClearS** At long last, we're down to the bare metal. This version of **ClearS**
is just 19 bytes long. That's just 37 percent as long as the original is just 19 bytes long. That's just 37 percent as long as the original

View file

@ -72,6 +72,7 @@ seen the features in action.
**LISTING 23.1 L23-1.ASM** **LISTING 23.1 L23-1.ASM**
```nasm
; Sample VGA program. ; Sample VGA program.
; Animates four balls bouncing around a playfield by using ; Animates four balls bouncing around a playfield by using
; page flipping. Playfield is panned smoothly both horizontally ; page flipping. Playfield is panned smoothly both horizontally
@ -627,3 +628,4 @@ seen the features in action.
AdjustPanning endp AdjustPanning endp
cseg ends cseg ends
end start end start
```

View file

@ -12,6 +12,7 @@ pages: 453-458
**LISTING 24.1 L24-1.ASM** **LISTING 24.1 L24-1.ASM**
```nasm
; Program to illustrate operation of ALUs and latches of the VGA's ; Program to illustrate operation of ALUs and latches of the VGA's
; Graphics Controller. Draws a variety of patterns against ; Graphics Controller. Draws a variety of patterns against
; a horizontally striped background, using each of the 4 available ; a horizontally striped background, using each of the 4 available
@ -263,3 +264,4 @@ pages: 453-458
DrawVerticalBox endp DrawVerticalBox endp
cseg ends cseg ends
end start end start
```

View file

@ -90,16 +90,19 @@ program code and a few cycles of execution time. DX is being loaded with
a word value that's composed of two independent immediate byte values. a word value that's composed of two independent immediate byte values.
The obvious way to implement this would be with The obvious way to implement this would be with
```nasm
MOV DL,VALUE1 MOV DL,VALUE1
MOV DH,VALUE2 MOV DH,VALUE2
```
which requires four instruction bytes. By shifting the value destined which requires four instruction bytes. By shifting the value destined
for the high byte into the high byte with MASM's shift-left operator, for the high byte into the high byte with MASM's shift-left operator,
**SHL** (\*100H would work also), and then logically combining the **SHL** (\*100H would work also), and then logically combining the
values with MASM's **OR** operator (or the **ADD** operator), both values with MASM's **OR** operator (or the **ADD** operator), both
halves of DX can be loaded with a single instruction, as in halves of DX can be loaded with a single instruction, as in
```nasm
MOV DX,(VALUE2 SHL 8) OR VALUE1 MOV DX,(VALUE2 SHL 8) OR VALUE1
```
which takes only three bytes and is faster, being a single instruction. which takes only three bytes and is faster, being a single instruction.
(Note, though, that in 32-bit protected mode, there's a size and (Note, though, that in 32-bit protected mode, there's a size and

View file

@ -12,6 +12,7 @@ pages: 466-470
**LISTING 25.1 L25-1.ASM** **LISTING 25.1 L25-1.ASM**
```nasm
; Program to illustrate operation of data rotate and bit mask ; Program to illustrate operation of data rotate and bit mask
; features of Graphics Controller. Draws 8x8 character at ; features of Graphics Controller. Draws 8x8 character at
; specified location, using VGA's 8x8 ROM font. Designed ; specified location, using VGA's 8x8 ROM font. Designed
@ -242,6 +243,7 @@ pages: 466-470
; ;
cseg ends cseg ends
end start end start
```
The bit mask can be used for much more than bit-aligned fonts. For The bit mask can be used for much more than bit-aligned fonts. For
example, the bit mask is useful for fast pixel drawing, such as that example, the bit mask is useful for fast pixel drawing, such as that

View file

@ -12,6 +12,7 @@ pages: 472-474
**LISTING 25.2 L25-2.ASM** **LISTING 25.2 L25-2.ASM**
```nasm
; Program to illustrate operation of Map Mask register when drawing ; Program to illustrate operation of Map Mask register when drawing
; to memory that already contains data. ; to memory that already contains data.
; By Michael Abrash. ; By Michael Abrash.
@ -93,6 +94,7 @@ pages: 472-474
start endp start endp
cseg ends cseg ends
end start end start
```
#### Setting All Planes to a Single Color {#Heading6} #### Setting All Planes to a Single Color {#Heading6}

View file

@ -12,6 +12,7 @@ pages: 474-478
**LISTING 25.3 L25-3.ASM** **LISTING 25.3 L25-3.ASM**
```nasm
; Program to illustrate operation of set/reset circuitry to force ; Program to illustrate operation of set/reset circuitry to force
; setting of memory that already contains data. ; setting of memory that already contains data.
; By Michael Abrash. ; By Michael Abrash.
@ -120,6 +121,7 @@ pages: 474-478
start endp start endp
cseg ends cseg ends
end start end start
```
#### Manipulating Planes Individually {#Heading7} #### Manipulating Planes Individually {#Heading7}
@ -142,6 +144,7 @@ be used to control individual pixels.
**LISTING 25.4 L25-4.ASM** **LISTING 25.4 L25-4.ASM**
```nasm
; Program to illustrate operation of set/reset circuitry in conjunction ; Program to illustrate operation of set/reset circuitry in conjunction
; with CPU data to modify setting of memory that already contains data. ; with CPU data to modify setting of memory that already contains data.
; By Michael Abrash. ; By Michael Abrash.
@ -250,3 +253,4 @@ be used to control individual pixels.
start endp start endp
cseg ends cseg ends
end start end start
```

View file

@ -12,6 +12,7 @@ pages: 484-489
**LISTING 26.1 L26-1.ASM** **LISTING 26.1 L26-1.ASM**
```nasm
; Program to illustrate operation of write mode 3 of the VGA. ; Program to illustrate operation of write mode 3 of the VGA.
; Draws 8x8 characters at arbitrary locations without disturbing ; Draws 8x8 characters at arbitrary locations without disturbing
; the background, using VGA's 8x8 ROM font. Designed ; the background, using VGA's 8x8 ROM font. Designed
@ -305,3 +306,4 @@ pages: 484-489
; ;
cseg ends cseg ends
end start end start
```

View file

@ -80,6 +80,7 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
**LISTING 26.2 L26-2.ASM** **LISTING 26.2 L26-2.ASM**
```nasm
; Program to illustrate high-speed text-drawing operation of ; Program to illustrate high-speed text-drawing operation of
; write mode 3 of the VGA. ; write mode 3 of the VGA.
; Draws a string of 8x14 characters at arbitrary locations ; Draws a string of 8x14 characters at arbitrary locations
@ -398,6 +399,7 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into
; ;
cseg ends cseg ends
end start end start
```
In this chapter, I've tried to give you a feel for how write mode 3 In this chapter, I've tried to give you a feel for how write mode 3
works and what it might be used for, rather than providing polished, works and what it might be used for, rather than providing polished,

View file

@ -52,6 +52,7 @@ image.
**LISTING 27.1 L27-1.ASM** **LISTING 27.1 L27-1.ASM**
```nasm
; Program to illustrate one use of write mode 2 of the VGA and EGA by ; Program to illustrate one use of write mode 2 of the VGA and EGA by
; animating the image of an "A" drawn by copying it from a chunky ; animating the image of an "A" drawn by copying it from a chunky
; bit-map in system memory to a planar bit-map in VGA or EGA memory. ; bit-map in system memory to a planar bit-map in VGA or EGA memory.
@ -269,3 +270,4 @@ image.
DrawFromChunkyBitmap endp DrawFromChunkyBitmap endp
Code ends Code ends
end Start end Start
```

View file

@ -53,6 +53,7 @@ the CPU byte in write mode 2 to select the color in which to draw.
**LISTING 27.2 L27-2.ASM** **LISTING 27.2 L27-2.ASM**
```nasm
; Program to illustrate one use of write mode 2 of the VGA and EGA by ; Program to illustrate one use of write mode 2 of the VGA and EGA by
; drawing lines in color patterns. ; drawing lines in color patterns.
; ;
@ -379,3 +380,4 @@ the CPU byte in write mode 2 to select the color in which to draw.
Start endp Start endp
Code ends Code ends
end Start end Start
```

View file

@ -53,6 +53,7 @@ rewarding!) VGA to cover.
**LISTING 27.3 L27-3.ASM** **LISTING 27.3 L27-3.ASM**
```nasm
; Program to illustrate flipping from bit-mapped graphics mode to ; Program to illustrate flipping from bit-mapped graphics mode to
; text mode and back without losing any of the graphics bit-map. ; text mode and back without losing any of the graphics bit-map.
; ;
@ -244,3 +245,4 @@ rewarding!) VGA to cover.
Start endp Start endp
Code ends Code ends
end Start end Start
```

View file

@ -12,6 +12,7 @@ pages: 526-530
**LISTING 28.1 L28-1.ASM** **LISTING 28.1 L28-1.ASM**
```nasm
; Program to illustrate the use of the Read Map register in read mode 0. ; Program to illustrate the use of the Read Map register in read mode 0.
; Animates by copying a 16-color image from VGA memory to system memory, ; Animates by copying a 16-color image from VGA memory to system memory,
; one plane at a time, then copying the image back to a new location ; one plane at a time, then copying the image back to a new location
@ -247,3 +248,4 @@ pages: 526-530
GetImageOffset endp GetImageOffset endp
code ends code ends
end Start end Start
```

Some files were not shown because too many files have changed in this diff Show more