159 lines
No EOL
6.9 KiB
Markdown
159 lines
No EOL
6.9 KiB
Markdown
### Notes on the Zen Timer {#Heading10}
|
||
|
||
The Zen timer subroutines are designed to be near-called from assembly
|
||
language code running in the public segment **Code**. The Zen timer
|
||
subroutines can, however, be called from any assembly or high-level
|
||
language code that generates OBJ files that are compatible with the
|
||
Microsoft linker, simply by modifying the segment that the timer code
|
||
runs in to match the segment used by the code being timed, or by
|
||
changing the Zen timer routines to far procedures and making far calls
|
||
to the Zen timer code from the code being timed, as discussed at the end
|
||
of this chapter. All three subroutines preserve all registers and all
|
||
flags except the interrupt flag, so calls to these routines are
|
||
transparent to the calling code.
|
||
|
||
If you do change the Zen timer routines to far procedures in order to
|
||
call them from code running in another segment, be sure to make *all*
|
||
the Zen timer routines far, including **ReferenceZTimerOn** and
|
||
**ReferenceZTimerOff**. (You'll have to put **FAR PTR** overrides on the
|
||
calls from **ZTimerOff** to the latter two routines if you do make them
|
||
far.) If the reference routines aren't the same type—near or far—as the
|
||
other routines, they won't reflect the true overhead incurred by
|
||
starting and stopping the Zen timer.
|
||
|
||
Please be aware that the inaccuracy that the Zen timer can introduce
|
||
into the system clock time does not affect the accuracy of the
|
||
performance measurements reported by the Zen timer itself. The 8253
|
||
counts once every 838 ns, giving us a count resolution of about 1µs,
|
||
although factors such as the prefetch queue (as discussed below),
|
||
dynamic RAM refresh, and internal timing variations in the 8253 make it
|
||
perhaps more accurate to describe the Zen timer as measuring code
|
||
performance with an accuracy of better than 10µs. In fact, the Zen timer
|
||
is actually most accurate in assessing code performance when timing
|
||
intervals longer than about 100 µs. At any rate, we're most interested
|
||
in using the Zen timer to assess the relative performance of various
|
||
code sequences—that is, using it to compare and tweak code—and the timer
|
||
is more than accurate enough for that purpose.
|
||
|
||
The Zen timer works on all PC-compatible computers I've tested it on,
|
||
including XTs, ATs, PS/2 computers, and 386, 486, and Pentium-based
|
||
machines. Of course, I haven't been able to test it on *all*
|
||
PC-compatibles, but I don't expect any problems; computers on which the
|
||
Zen timer doesn't run can't truly be called "PC-compatible."
|
||
|
||
On the other hand, there is certainly no guarantee that code performance
|
||
as measured by the Zen timer will be the same on compatible computers as
|
||
on genuine IBM machines, or that either absolute or relative code
|
||
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
|
||
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
|
||
way without actually checking.
|
||
|
||
Not that this variation between models makes the Zen timer one whit less
|
||
useful—quite the contrary. The Zen timer is an excellent tool for
|
||
evaluating code performance over the entire spectrum of PC-compatible
|
||
computers.
|
||
|
||
### A Sample Use of the Zen Timer {#Heading11}
|
||
|
||
Listing 3.2 shows a test-bed program for measuring code performance with
|
||
the Zen timer. This program sets DS equal to CS (for reasons we'll
|
||
discuss shortly), includes the code to be measured from the file
|
||
TESTCODE, and calls **ZTimerReport** to display the timing results.
|
||
Consequently, the code being measured should be in the file TESTCODE,
|
||
and should contain calls to **ZTimerOn** and **ZTimerOff** .
|
||
|
||
**LISTING 3.2 PZTEST.ASM**
|
||
|
||
; Program to measure performance of code that takes less than
|
||
; 54 ms to execute. (PZTEST.ASM)
|
||
;
|
||
; Link with PZTIMER.ASM (Listing 3.1). PZTEST.BAT (Listing 3.4)
|
||
; can be used to assemble and link both files. Code to be
|
||
; measured must be in the file TESTCODE; Listing 3.3 shows
|
||
; a sample TESTCODE file.
|
||
;
|
||
; By Michael Abrash
|
||
;
|
||
mystack segment para stack ‘STACK'
|
||
db 512 dup(?)
|
||
mystack ends
|
||
;
|
||
Code segment para public ‘CODE'
|
||
assume cs:Code, ds:Code
|
||
extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near
|
||
Start proc near
|
||
push cs
|
||
pop ds ; set DS to point to the code segment,
|
||
; so data as well as code can easily
|
||
; be included in TESTCODE
|
||
;
|
||
include TESTCODE ;code to be measured, including
|
||
; calls to ZTimerOn and ZTimerOff
|
||
;
|
||
; Display the results.
|
||
;
|
||
call ZTimerReport
|
||
;
|
||
; Terminate the program.
|
||
;
|
||
mov ah,4ch
|
||
int 21h
|
||
Start endp
|
||
Code ends
|
||
end Start
|
||
|
||
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
|
||
**MemVar** . Note that Listing 3.3 calls **ZTimerOn** to start timing,
|
||
performs 1,000 **MOV** instructions in a row, and calls **ZTimerOff** to
|
||
end timing. When Listing 3.2 is named TESTCODE and included by Listing
|
||
3.3, Listing 3.2 calls **ZTimerReport** to display the execution time
|
||
after the code in Listing 3.3 has been run.
|
||
|
||
**LISTING 3.3 LST3-3.ASM**
|
||
|
||
; Test file;
|
||
; Measures the performance of 1,000 loads of AL from
|
||
; memory. (Use by renaming to TESTCODE, which is
|
||
; included by PZTEST.ASM (Listing 3.2). PZTIME.BAT
|
||
; (Listing 3.4) does this, along with all assembly
|
||
; and linking.)
|
||
;
|
||
jmp Skip ;jump around defined data
|
||
;
|
||
MemVar db ?
|
||
;
|
||
Skip:
|
||
;
|
||
; Start timing.
|
||
;
|
||
call ZTimerOn
|
||
;
|
||
rept 1000
|
||
mov al,[MemVar]
|
||
endm
|
||
;
|
||
; Stop timing.
|
||
;
|
||
call ZTimerOff
|
||
|
||
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
|
||
in its entirety for each code fragment we want to measure; by defining
|
||
any needed data right in the code segment and jumping around that data,
|
||
each listing becomes self-contained and can be plugged directly into
|
||
Listing 3.2 as TESTCODE. Listing 3.2 sets DS equal to CS before doing
|
||
anything else precisely so that data can be embedded in code fragments
|
||
being timed. Note that only after the initial jump is performed in
|
||
Listing 3.3 is the Zen timer started, since we don't want to include the
|
||
execution time of start-up code in the timing interval. That's why the
|
||
calls to **ZTimerOn** and **ZTimerOff** are in TESTCODE, not in
|
||
PZTEST.ASM; this way, we have full control over which portion of
|
||
TESTCODE is timed, and we can keep set-up code and the like out of the
|
||
timing interval. |