From 340429d486a969dec509728cb8eb59acff75f754 Mon Sep 17 00:00:00 2001 From: Michael Stum Date: Mon, 27 Jan 2014 22:49:56 -0800 Subject: [PATCH 1/3] Chapter 1 typos --- src/chapter-01.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/chapter-01.md b/src/chapter-01.md index fc56d44..91f721a 100644 --- a/src/chapter-01.md +++ b/src/chapter-01.md @@ -254,7 +254,7 @@ requires over two and one-half minutes to checksum *one* file! These results make it clear that it's folly to rely on your compiler's optimization to make your programs fast. Listing 1.1 is simply poorly designed, and no amount of compiler optimization will compensate for -that failing. To drive home the point, conListings 1.2 and 1.3, which +that failing. To drive home the point, Listings 1.2 and 1.3, which together are equivalent to Listing 1.1 except that the entire checksum loop is written in tight assembly code. The assembly language implementation is indeed faster than any of the C versions, as shown in @@ -419,7 +419,7 @@ Listing 1.1. How can we speed up Listing 1.1? It should be clear that we must somehow avoid invoking DOS for every byte in the file, and that means reading more than one byte at a time, then buffering the data and parceling it -out for examination one byte at a time. By gosh, that's a description of +out for examination one byte at a time. My gosh, that's a description of C's stream I/O feature, whereby C reads files in chunks and buffers the bytes internally, doling them out to the application as needed by reading them from memory rather than calling DOS. Let's try using stream @@ -500,7 +500,7 @@ your programs that directly affect response time. Notice, for example, that I haven't bothered to implement a version of the checksum program entirely in assembly; Listings 1.2 and 1.6 call assembly subroutines that handle the time-critical operations, but C is still used for -checking command-line parameters, operning files, printing, and the +checking command-line parameters, opening files, printing, and the like. > ![](images/i.jpg) @@ -521,7 +521,7 @@ Listing 1.4 is good, but let's see if there are other—perhaps less obvious—ways to get the same results faster. Let's start by considering why Listing 1.4 is so much better than Listing 1.1. Like `read()`, `getc()` calls DOS to read from the file; the speed improvement of -Listing 1.4 over Listing 1.1 occurs because `getc()` eads many bytes +Listing 1.4 over Listing 1.1 occurs because `getc()` reads many bytes at once via DOS, then manages those bytes for us. That's faster than reading them one at a time using `read()`—but there's no reason to think that it's faster than having our program read and manage blocks @@ -563,7 +563,7 @@ The third reason is often fallacious. C library functions are not always written in assembly, nor are they always particularly well-optimized. (In fact, they're often written for *portability*, which has nothing to do with optimization.) What's more, they're general-purpose functions, -and often can be outperformed by well-but-not- brilliantly-written code +and often can be outperformed by well-but-not-brilliantly-written code that is well-matched to a specific task. As an example, consider Listing 1.5, which uses internal buffering to handle blocks of bytes at a time. Table 1.1 shows that Listing 1.5 is 2.5 to 4 times faster than Listing @@ -667,7 +667,7 @@ indeed make a significant difference. Table 1.1 indicates that the optimized version of Listing 1.5 produced by Microsoft C outperforms an unoptimized version of the same code by more than 60 percent. What's more, a mostly-assembly version of Listing 1.5, shown in Listings 1.6 -and 1.7, outperforms even the best-optimized C version of List1.5 by 26 +and 1.7, outperforms even the best-optimized C version of Listing 1.5 by 26 percent. These are considerable improvements, well worth pursuing—once the design has been maxed out. From e54d0fa5d262a7ba6932e5ede7293c607e571e2e Mon Sep 17 00:00:00 2001 From: Michael Stum Date: Mon, 27 Jan 2014 23:04:19 -0800 Subject: [PATCH 2/3] Chapter 2 typos --- src/chapter-02.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chapter-02.md b/src/chapter-02.md index 5143edb..c283050 100644 --- a/src/chapter-02.md +++ b/src/chapter-02.md @@ -293,7 +293,7 @@ all the more so given that compilers are constrained by the limitations of high-level languages and by the process of transformation from high-level to machine language. Consequently, carefully optimized assembly is not just the language of choice but the *only* choice for -the 1percent to 10 percent of code—usually consisting of small, +the 1 percent to 10 percent of code—usually consisting of small, well-defined subroutines—that determines overall program performance, and it is the only choice for code that must be as compact as possible, as well. In the run-of-the-mill, non-time-critical portions of your From 753657938b57fa9bc3e5c635c70c863d15cf4c55 Mon Sep 17 00:00:00 2001 From: Michael Stum Date: Tue, 28 Jan 2014 00:18:10 -0800 Subject: [PATCH 3/3] Chapter 3 typos (and 1 Chapter 2 one) --- src/chapter-02.md | 2 +- src/chapter-03.md | 160 +++++++++++++++++++++++----------------------- 2 files changed, 81 insertions(+), 81 deletions(-) diff --git a/src/chapter-02.md b/src/chapter-02.md index c283050..25baba6 100644 --- a/src/chapter-02.md +++ b/src/chapter-02.md @@ -200,7 +200,7 @@ enough. The single most critical aspect of the hardware, and the one about which it is hardest to learn, is the CPU. The x86 family CPUs have a complex, irregular instruction set, and, unlike most processors, they are neither -straightforward nor wellregarding true code performance. What's more, +straightforward nor well-documented true code performance. What's more, assembly is so difficult to learn that most articles and books that present assembly code settle for code that just works, rather than code that pushes the CPU to its limits. In fact, since most articles and diff --git a/src/chapter-03.md b/src/chapter-03.md index 2afe5ce..0b91926 100644 --- a/src/chapter-03.md +++ b/src/chapter-03.md @@ -671,7 +671,7 @@ count reaches zero, the timer turns over and starts counting down again without stopping, and a pulse is generated for a single clock period. While the pulse is not held for nearly as long as in square wave mode, it doesn't matter, since the 8259 interrupt controller is configured in -the PC to be edgeand hence cares only about the existence of a pulse +the PC to be edge-triggered and hence cares only about the existence of a pulse from timer 0, not the duration of the pulse. As a result, timer 0 continues to generate timer interrupts in divide-by-N mode, and the system clock continues to maintain good time. @@ -688,7 +688,7 @@ the Zen timer shown in Listing 3.1 supports. In fact, the Zen timer shown in Listing 3.1 can only time intervals of up to about 54 ms in length, since that is the period of time that can be measured by timer 0 before its count turns over and repeats. -fifty-four ms may not seem like a very long time, but even a CPU as slow +Fifty-four ms may not seem like a very long time, but even a CPU as slow as the 8088 can perform more than 1,000 divides in 54 ms, and division is the single instruction that the 8088 performs most slowly. If a measured period turns out to be longer than 54 ms (that is, if timer 0 @@ -730,11 +730,11 @@ restart until the timing interval ends, losing time all the while. The effects on the system time of the Zen timer aren't a matter for great concern, as they are temporary, lasting only until the next warm -or cold boot. System that have batteryclocks, (AT-style machines; that +or cold boot. System that have battery-backed clocks, (AT-style machines; that is, virtually all machines in common use) automatically reset the correct time whenever the computer is booted, and systems without -battery-clocks prompt for the correct date and time when booted. -Also,repeated use of the Zen timer usually makes the system clock slow +battery-backed clocks prompt for the correct date and time when booted. +Also, repeated use of the Zen timer usually makes the system clock slow by at most a total of a few seconds, unless code that takes much longer than 54 ms to run is timed (in which case the Zen timer will notify you that the code is too long to time). @@ -789,8 +789,8 @@ from timer counts to microseconds, and prints the resulting time in microseconds to the standard output. Note that `ZTimerReport` need not be called immediately after -`ZTimerOff`. In fact, after a given call to `ZTimerOff, -ZTimerReport` can be called at any time right up until the next call to +`ZTimerOff`. In fact, after a given call to `ZTimerOff`, +`ZTimerReport` can be called at any time right up until the next call to `ZTimerOn`. You may want to use the Zen timer to measure several portions of a @@ -880,7 +880,7 @@ performance will be similar even on different IBM models; in fact, quite the opposite is true. For example, every PS/2 computer, even the relatively slow Model 30, executes code much faster than does a PC or XT. As another example, I set out to do the timings for my earlier book -*Zen of Assembly Language* on an XTcomputer, only to find that the +*Zen of Assembly Language* on an XT-compatible computer, only to find that the computer wasn't quite IBM-compatible regarding code performance. The differences were minor, mind you, but my experience illustrates the risk of assuming that a specific make of computer will perform in a certain @@ -996,7 +996,7 @@ timing interval. Listing 3.3 is used by naming it TESTCODE, assembling both Listing 3.2 (which includes TESTCODE) and Listing 3.1 with TASM or MASM, and linking -the two resulting OBJ files together by way of the Borland orMicrosoft +the two resulting OBJ files together by way of the Borland or Microsoft linker. Listing 3.4 shows a batch file, PZTIME.BAT, which does all that; when run, this batch file generates and runs the executable file PZTEST.EXE. PZTIME.BAT (Listing 3.4) assumes that the file PZTIMER.ASM @@ -1111,8 +1111,8 @@ pztime 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 you wish to time code directly in place in your programs, rather than in -the test-bed program of Listing 3.2, simply insert calls to `ZTimerOn, -ZTimerOff`, and `ZTimerReport` in the appropriate places and link +the test-bed program of Listing 3.2, simply insert calls to `ZTimerOn`, +`ZTimerOff`, and `ZTimerReport` in the appropriate places and link PZTIMER to your program. ### The Long-Period Zen Timer @@ -1303,8 +1303,8 @@ computers. ; All registers and all flags are preserved by all routines. ; -Code segment word public ‘CODE' - assume cs: Code, ds:nothing +Code segment word public 'CODE' + assume cs:Code, ds:nothing public ZTimerOn, ZTimerOff, ZTimerReport ; @@ -1326,24 +1326,24 @@ Code segment word public ‘CODE' ; which support the undocumented timer-stopping feature of the ; 8253. The choice is yours. ; -PS2 equ1 +PS2 equ 1 ; ; Base address of the 8253 timer chip. ; -BASE_8253 equ40h +BASE_8253 equ 40h ; ; The address of the timer 0 count registers in the 8253. ; -TIMER_0_8253 equBASE_8253 + 0 +TIMER_0_8253 equ BASE_8253 + 0 ; ; The address of the mode register in the 8253. ; -MODE_8253 equBASE_8253 + 3 +MODE_8253 equ BASE_8253 + 3 ; ; The address of the BIOS timer count variable in the BIOS ; data segment. ; -TIMER_COUNT equ46ch +TIMER_COUNT equ 46ch ; ; Macro to emulate a POPF instruction in order to fix the bug in some ; 80286 chips which allows interrupts to occur during a POPF even when @@ -1353,9 +1353,9 @@ MPOPF macro local p1, p2 jmp short p2 p1: iret ;jump to pushed address & pop flags -p2: pushcs ;construct far return address to +p2: push cs ;construct far return address to call p1 ; the next instruction -endm + endm ; ; Macro to delay briefly to ensure that enough time has elapsed @@ -1383,11 +1383,11 @@ ReferenceCount dw ? ;number of counts required to ; ; String printed to report results. ; -OutputStr labelbyte - db 0dh, 0ah, ‘Timed count: ‘ -TimedCountStr db10 dup (?) - db' microseconds', 0dh, 0ah - db ‘$' +OutputStr label byte + db 0dh, 0ah, 'Timed count: ' +TimedCountStr db 10 dup (?) + db ' microseconds', 0dh, 0ah + db '$' ; ; Temporary storage for timed count as it's divided down by powers ; of ten when converting from doubleword binary to ASCII. @@ -1398,7 +1398,7 @@ CurrentCountHigh dw ? ; Powers of ten table used to perform division by 10 when doing ; doubleword conversion from binary to ASCII. ; -PowersOfTenlabelword +PowersOfTen label word dd 1 dd 10 dd 100 @@ -1409,33 +1409,33 @@ PowersOfTenlabelword dd 10000000 dd 100000000 dd 1000000000 -PowersOfTenEnd labelword +PowersOfTenEnd label word ; ; String printed to report that the high word of the BIOS count ; changed while timing (an hour elapsed or midnight was crossed), ; and so the count is invalid and the test needs to be rerun. ; -TurnOverStrlabelbyte - db 0dh, 0ah - db ‘****************************************************' +TurnOverStr label byte db 0dh, 0ah - db'* Either midnight passed or an hour or more passed *' + db '****************************************************' db 0dh, 0ah - db'* while timing was in progress. If the former was *' + db '* Either midnight passed or an hour or more passed *' db 0dh, 0ah - db'* the case, please rerun the test; if the latter *' + db '* while timing was in progress. If the former was *' db 0dh, 0ah - db'* was the case, the test code takes too long to *' + db '* the case, please rerun the test; if the latter *' db 0dh, 0ah - db'* run to be timed by the long-period Zen timer. *' + db '* was the case, the test code takes too long to *' db 0dh, 0ah - db ‘* Suggestions: use the DOS TIME command, the DOS *' + db '* run to be timed by the long-period Zen timer. *' db 0dh, 0ah - db ‘* time function, or a watch. *' + db '* Suggestions: use the DOS TIME command, the DOS *' db 0dh, 0ah - db ‘****************************************************' + db '* time function, or a watch. *' db 0dh, 0ah - db'$' + db '****************************************************' + db 0dh, 0ah + db '$' ;******************************************************************** ;* Routine called to start timing. * @@ -1447,7 +1447,7 @@ ZTimerOn proc near ; Save the context of the program being timed. ; push ax - pus hf + pushf ; ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause ; linear counting rather than count-by-two counting. Also stops @@ -1463,10 +1463,10 @@ ZTimerOn proc near ; clock count each time it is executed. ; DELAY - subal,al - outTIMER_0_8253,al ;lsb + sub al,al + out TIMER_0_8253,al ;lsb DELAY - outTIMER_0_8253,al ;msb + out TIMER_0_8253,al ;msb ; ; In case interrupts are disabled, enable interrupts briefly to allow ; the interrupt generated when switching from mode 3 to mode 2 to be @@ -1488,12 +1488,12 @@ ZTimerOn proc near ; interrupts in order to avoid getting a half-changed count.) ; push ds - subax, ax - movds, ax - movax, ds:[TIMER_COUNT+2] - movcs: [StartBIOSCountHigh],ax - movax, ds:[TIMER_COUNT] - movcs: [StartBIOSCountLow],ax + sub ax, ax + mov ds, ax + mov ax, ds:[TIMER_COUNT+2] + mov cs:[StartBIOSCountHigh],ax + mov ax, ds:[TIMER_COUNT] + mov cs:[StartBIOSCountLow],ax pop ds ; ; Set the timer count to 0 again to start the timing interval. @@ -1501,7 +1501,7 @@ ZTimerOn proc near mov al,00110100b ;set up to load initial out MODE_8253,al ; timer count DELAY - subal, al + sub al, al out TIMER_0_8253,al; load count lsb DELAY out TIMER_0_8253,al; load count msb @@ -1512,20 +1512,20 @@ ZTimerOn proc near popax ret -ZTimerOnendp +ZTimerOn endp ;******************************************************************** ;* Routine called to stop timing and get count. * ;******************************************************************** -ZTimerOff procnear +ZTimerOff proc near ; ; Save the context of the program being timed. ; pushf - pushax - pushcx + push ax + push cx ; ; In case interrupts are disabled, enable interrupts briefly to allow ; any pending timer interrupt to be handled. Interrupts must be @@ -1618,7 +1618,7 @@ ife PS2 endif -sti;let the BIOS count continue + sti ;let the BIOS count continue ; ; Time a zero-length code fragment, to get a reference for how ; much overhead this routine has. Time it 16 times and average it, @@ -1633,14 +1633,14 @@ RefLoop: call ReferenceZTimerOff loop RefLoop sti - add cs:[ReferenceCount],8; total + (0.5 * 16) + add cs:[ReferenceCount],8 ;total + (0.5 * 16) mov cl,4 - shr cs:[ReferenceCount],cl;(total) / 16 + 0.5 + shr cs:[ReferenceCount],cl ;(total) / 16 + 0.5 ; ; Restore the context of the program being timed and return to it. ; - popcx - popax + pop cx + pop ax MPOPF ret @@ -1650,11 +1650,11 @@ ZTimerOff endp ; Called by ZTimerOff to start the timer for overhead measurements. ; -ReferenceZTimerOnprocnear +ReferenceZTimerOn proc near ; ; Save the context of the program being timed. ; - pushax + push ax pushf ; ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause @@ -1677,7 +1677,7 @@ ReferenceZTimerOnprocnear popax ret -ReferenceZTimerOnendp +ReferenceZTimerOn endp ; ; Called by ZTimerOff to stop the timer and add the result to @@ -1686,20 +1686,20 @@ ReferenceZTimerOnendp ; isn't going to take anywhere near 54 ms. ; -ReferenceZTimerOff procnear +ReferenceZTimerOff proc near ; ; Save the context of the program being timed. ; pushf - pushax - pushcx + push ax + push cx ; ; Match the interrupt-window delay in ZTimerOff. ; sti - rept10 - jmp$+2 + rept 10 + jmp $+2 endm mov al,00000000b @@ -1720,8 +1720,8 @@ ReferenceZTimerOff procnear ; ; Restore the context and return. ; - popcx - popax + pop cx + pop ax MPOPF ret @@ -1731,7 +1731,7 @@ ReferenceZTimerOff endp ;* Routine called to report timing results. * ;******************************************************************** -ZTimerReportprocnear +ZTimerReport proc near pushf push ax @@ -1741,7 +1741,7 @@ ZTimerReportprocnear push si push di push ds - ; +; push cs ;DOS functions require that DS point pop ds ; to text to be displayed on the screen assume ds :Code @@ -1780,7 +1780,7 @@ TestTooLong: ; Convert the BIOS time to microseconds. ; CalcBIOSTime: - mov ax,[EndBIOSCountLow] + mov ax,[EndBIOSCountLow] sub ax,[StartBIOSCountLow] mov dx,54925 ;number of microseconds each ; BIOS count represents @@ -1808,7 +1808,7 @@ CalcBIOSTime: mov si,8381 ;convert the reference count mul si ; to microseconds mov si,10000 - div si;* .8381 = * 8381 / 10000 + div si ;* .8381 = * 8381 / 10000 sub bx,ax sbb cx,0 mov [CurrentCountLow],bx @@ -1896,7 +1896,7 @@ substantially. Finally, please note that the *precision* Zen timer works perfectly well on both PS/2 and non-PS/2 computers. The PS/2 and 8253 considerations -we've just discussed apply *only* to the longZen timer. +we've just discussed apply *only* to the long-period Zen timer. ### Example Use of the Long-Period Zen Timer @@ -2126,7 +2126,7 @@ be dealt with here: small code model and large; I'll tackle the simpler one, the small code model, first. Altering the Zen timer for linking to a small code model C program -involves the following steps: `C` hange `ZTimerOn` to +involves the following steps: Change `ZTimerOn` to `_ZTimerOn`, change `ZTimerOff` to `_ZTimerOff`, change `ZTimerReport` to `_ZTimerReport`, and change `Code` to `_TEXT` . Figure 3.2 shows the line numbers and new states of all @@ -2187,16 +2187,16 @@ push cs 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 is normally a great optimization, being both smaller and faster than a -far call. However, it's not so great for the Zen +far call. ![**Figure 3.3**  *Changes for use with large code model C.*](images/03-03.jpg) -timer, because our purpose in calling the reference timing code is to +However, it's not so great for the Zen timer, because our purpose in calling the reference timing code is to determine exactly how much time is taken by overhead code—including the -far calls to `ZTimerOn` and `ZTimerOf`f! By converting the far calls +far calls to `ZTimerOn` and `ZTimerOf`! By converting the far calls to push/near call pairs within the Zen timer module, TASM makes it impossible to emulate exactly the overhead of the Zen timer, and makes timings slightly (about 16 cycles on a 386) less accurate. @@ -2255,7 +2255,7 @@ processor cache at the start of the code being timed, because the timing code is not necessarily fetched and does not necessarily access memory in exactly the same time sequence as the code immediately preceding the code under measurement normally does. This prefetch effect can introduce -as much as 3 to 4 µ of inaccuracy. Similarly, the state of the prefetch +as much as 3 to 4 µs of inaccuracy. Similarly, the state of the prefetch queue at the end of the code being timed affects how long the code that stops the timer takes to execute. Consequently, the Zen timer tends to be more accurate for longer code sequences, since the relative magnitude