home *** CD-ROM | disk | FTP | other *** search
- ;
- ; *** Listing 2-5 ***
- ;
- ; The long-period Zen timer. (LZTIMER.ASM)
- ; Uses the 8253 timer and the BIOS time-of-day count to time the
- ; performance of code that takes less than an hour to execute.
- ; Because interrupts are left on (in order to allow the timer
- ; interrupt to be recognized), this is less accurate than the
- ; precision Zen timer, so it is best used only to time code that takes
- ; more than about 54 milliseconds to execute (code that the precision
- ; Zen timer reports overflow on). Resolution is limited by the
- ; occurrence of timer interrupts.
- ;
- ; By Michael Abrash 4/26/89
- ;
- ; Externally callable routines:
- ;
- ; ZTimerOn: Saves the BIOS time of day count and starts the
- ; long-period Zen timer.
- ;
- ; ZTimerOff: Stops the long-period Zen timer and saves the timer
- ; count and the BIOS time-of-day count.
- ;
- ; ZTimerReport: Prints the time that passed between starting and
- ; stopping the timer.
- ;
- ; Note: If either more than an hour passes or midnight falls between
- ; calls to ZTimerOn and ZTimerOff, an error is reported. For
- ; timing code that takes more than a few minutes to execute,
- ; either the DOS TIME command in a batch file before and after
- ; execution of the code to time or the use of the DOS
- ; time-of-day function in place of the long-period Zen timer is
- ; more than adequate.
- ;
- ; Note: The PS/2 version is assembled by setting the symbol PS2 to 1.
- ; PS2 must be set to 1 on PS/2 computers because the PS/2's
- ; timers are not compatible with an undocumented timer-stopping
- ; feature of the 8253; the alternative timing approach that
- ; must be used on PS/2 computers leaves a short window
- ; during which the timer 0 count and the BIOS timer count may
- ; not be synchronized. You should also set the PS2 symbol to
- ; 1 if you're getting erratic or obviously incorrect results.
- ;
- ; Note: When PS2 is 0, the code relies on an undocumented 8253
- ; feature to get more reliable readings. It is possible that
- ; the 8253 (or whatever chip is emulating the 8253) may be put
- ; into an undefined or incorrect state when this feature is
- ; used.
- ;
- ; ***************************************************************
- ; * If your computer displays any hint of erratic behavior *
- ; * after the long-period Zen timer is used, such as the floppy *
- ; * drive failing to operate properly, reboot the system, set *
- ; * PS2 to 1 and leave it that way! *
- ; ***************************************************************
- ;
- ; Note: Each block of code being timed should ideally be run several
- ; times, with at least two similar readings required to
- ; establish a true measurement, in order to eliminate any
- ; variability caused by interrupts.
- ;
- ; Note: Interrupts must not be disabled for more than 54 ms at a
- ; stretch during the timing interval. Because interrupts
- ; are enabled, keys, mice, and other devices that generate
- ; interrupts should not be used during the timing interval.
- ;
- ; Note: Any extra code running off the timer interrupt (such as
- ; some memory-resident utilities) will increase the time
- ; measured by the Zen timer.
- ;
- ; Note: These routines can introduce inaccuracies of up to a few
- ; tenths of a second into the system clock count for each
- ; code section timed. Consequently, it's a good idea to
- ; reboot at the conclusion of timing sessions. (The
- ; battery-backed clock, if any, is not affected by the Zen
- ; timer.)
- ;
- ; All registers and all flags are preserved by all routines.
- ;
-
- Code segment word public 'CODE'
- assume cs:Code, ds:nothing
- public ZTimerOn, ZTimerOff, ZTimerReport
-
- ;
- ; Set PS2 to 0 to assemble for use on a fully 8253-compatible
- ; system; when PS2 is 0, the readings are more reliable if the
- ; computer supports the undocumented timer-stopping feature,
- ; but may be badly off if that feature is not supported. In
- ; fact, timer-stopping may interfere with your computer's
- ; overall operation by putting the 8253 into an undefined or
- ; incorrect state. Use with caution!!!
- ;
- ; Set PS2 to 1 to assemble for use on non-8253-compatible
- ; systems, including PS/2 computers; when PS2 is 1, readings
- ; may occasionally be off by 54 ms, but the code will work
- ; properly on all systems.
- ;
- ; A setting of 1 is safer and will work on more systems,
- ; while a setting of 0 produces more reliable results in systems
- ; which support the undocumented timer-stopping feature of the
- ; 8253. The choice is yours.
- ;
- PS2 equ 1
- ;
- ; Base address of the 8253 timer chip.
- ;
- BASE_8253 equ 40h
- ;
- ; The address of the timer 0 count registers in the 8253.
- ;
- TIMER_0_8253 equ BASE_8253 + 0
- ;
- ; The address of the mode register in the 8253.
- ;
- MODE_8253 equ BASE_8253 + 3
- ;
- ; The address of the BIOS timer count variable in the BIOS
- ; data segment.
- ;
- 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
- ; interrupts remain disabled.
- ;
- MPOPF macro
- local p1, p2
- jmp short p2
- p1: iret ;jump to pushed address & pop flags
- p2: push cs ;construct far return address to
- call p1 ; the next instruction
- endm
-
- ;
- ; Macro to delay briefly to ensure that enough time has elapsed
- ; between successive I/O accesses so that the device being accessed
- ; can respond to both accesses even on a very fast PC.
- ;
- DELAY macro
- jmp $+2
- jmp $+2
- jmp $+2
- endm
-
- StartBIOSCountLow dw ? ;BIOS count low word at the
- ; start of the timing period
- StartBIOSCountHigh dw ? ;BIOS count high word at the
- ; start of the timing period
- EndBIOSCountLow dw ? ;BIOS count low word at the
- ; end of the timing period
- EndBIOSCountHigh dw ? ;BIOS count high word at the
- ; end of the timing period
- EndTimedCount dw ? ;timer 0 count at the end of
- ; the timing period
- ReferenceCount dw ? ;number of counts required to
- ; execute timer overhead code
- ;
- ; String printed to report results.
- ;
- 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.
- ;
- CurrentCountLow dw ?
- CurrentCountHigh dw ?
- ;
- ; Powers of ten table used to perform division by 10 when doing
- ; doubleword conversion from binary to ASCII.
- ;
- PowersOfTen label word
- dd 1
- dd 10
- dd 100
- dd 1000
- dd 10000
- dd 100000
- dd 1000000
- dd 10000000
- dd 100000000
- dd 1000000000
- 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.
- ;
- TurnOverStr label byte
- db 0dh, 0ah
- db '****************************************************'
- db 0dh, 0ah
- db '* Either midnight passed or an hour or more passed *'
- db 0dh, 0ah
- db '* while timing was in progress. If the former was *'
- db 0dh, 0ah
- db '* the case, please rerun the test; if the latter *'
- db 0dh, 0ah
- db '* was the case, the test code takes too long to *'
- db 0dh, 0ah
- db '* run to be timed by the long-period Zen timer. *'
- db 0dh, 0ah
- db '* Suggestions: use the DOS TIME command, the DOS *'
- db 0dh, 0ah
- db '* time function, or a watch. *'
- db 0dh, 0ah
- db '****************************************************'
- db 0dh, 0ah
- db '$'
-
- ;********************************************************************
- ;* Routine called to start timing. *
- ;********************************************************************
-
- ZTimerOn proc near
-
- ;
- ; Save the context of the program being timed.
- ;
- push ax
- 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
- ; timer 0 until the timer count is loaded, except on PS/2
- ; computers.
- ;
- mov al,00110100b ;mode 2
- out MODE_8253,al
- ;
- ; Set the timer count to 0, so we know we won't get another
- ; timer interrupt right away.
- ; Note: this introduces an inaccuracy of up to 54 ms in the system
- ; clock count each time it is executed.
- ;
- DELAY
- sub al,al
- out TIMER_0_8253,al ;lsb
- DELAY
- 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
- ; recognized. Interrupts must be enabled for at least 210 ns to allow
- ; time for that interrupt to occur. Here, 10 jumps are used for the
- ; delay to ensure that the delay time will be more than long enough
- ; even on a very fast PC.
- ;
- pushf
- sti
- rept 10
- jmp $+2
- endm
- MPOPF
- ;
- ; Store the timing start BIOS count.
- ; (Since the timer count was just set to 0, the BIOS count will
- ; stay the same for the next 54 ms, so we don't need to disable
- ; interrupts in order to avoid getting a half-changed count.)
- ;
- push ds
- 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.
- ;
- mov al,00110100b ;set up to load initial
- out MODE_8253,al ; timer count
- DELAY
- sub al,al
- out TIMER_0_8253,al ;load count lsb
- DELAY
- out TIMER_0_8253,al ;load count msb
- ;
- ; Restore the context of the program being timed and return to it.
- ;
- MPOPF
- pop ax
- ret
-
- ZTimerOn endp
-
- ;********************************************************************
- ;* Routine called to stop timing and get count. *
- ;********************************************************************
-
- ZTimerOff proc near
-
- ;
- ; Save the context of the program being timed.
- ;
- pushf
- push ax
- push cx
- ;
- ; In case interrupts are disabled, enable interrupts briefly to allow
- ; any pending timer interrupt to be handled. Interrupts must be
- ; enabled for at least 210 ns to allow time for that interrupt to
- ; occur. Here, 10 jumps are used for the delay to ensure that the
- ; delay time will be more than long enough even on a very fast PC.
- ;
- sti
- rept 10
- jmp $+2
- endm
-
- ;
- ; Latch the timer count.
- ;
-
- if PS2
-
- mov al,00000000b
- out MODE_8253,al ;latch timer 0 count
- ;
- ; This is where a one-instruction-long window exists on the PS/2.
- ; The timer count and the BIOS count can lose synchronization;
- ; since the timer keeps counting after it's latched, it can turn
- ; over right after it's latched and cause the BIOS count to turn
- ; over before interrupts are disabled, leaving us with the timer
- ; count from before the timer turned over coupled with the BIOS
- ; count from after the timer turned over. The result is a count
- ; that's 54 ms too long.
- ;
-
- else
-
- ;
- ; Set timer 0 to mode 2 (divide-by-N), waiting for a 2-byte count
- ; load, which stops timer 0 until the count is loaded. (Only works
- ; on fully 8253-compatible chips.)
- ;
- mov al,00110100b ;mode 2
- out MODE_8253,al
- DELAY
- mov al,00000000b ;latch timer 0 count
- out MODE_8253,al
-
- endif
-
- cli ;stop the BIOS count
- ;
- ; Read the BIOS count. (Since interrupts are disabled, the BIOS
- ; count won't change.)
- ;
- push ds
- sub ax,ax
- mov ds,ax
- mov ax,ds:[TIMER_COUNT+2]
- mov cs:[EndBIOSCountHigh],ax
- mov ax,ds:[TIMER_COUNT]
- mov cs:[EndBIOSCountLow],ax
- pop ds
- ;
- ; Read the timer count and save it.
- ;
- in al,TIMER_0_8253 ;lsb
- DELAY
- mov ah,al
- in al,TIMER_0_8253 ;msb
- xchg ah,al
- neg ax ;convert from countdown
- ; remaining to elapsed
- ; count
- mov cs:[EndTimedCount],ax
- ;
- ; Restart timer 0, which is still waiting for an initial count
- ; to be loaded.
- ;
-
- ife PS2
-
- DELAY
- mov al,00110100b ;mode 2, waiting to load a
- ; 2-byte count
- out MODE_8253,al
- DELAY
- sub al,al
- out TIMER_0_8253,al ;lsb
- DELAY
- mov al,ah
- out TIMER_0_8253,al ;msb
- DELAY
-
- endif
-
- 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,
- ; for accuracy, rounding the result.
- ;
- mov cs:[ReferenceCount],0
- mov cx,16
- cli ;interrupts off to allow a
- ; precise reference count
- RefLoop:
- call ReferenceZTimerOn
- call ReferenceZTimerOff
- loop RefLoop
- sti
- add cs:[ReferenceCount],8 ;total + (0.5 * 16)
- mov cl,4
- shr cs:[ReferenceCount],cl ;(total) / 16 + 0.5
- ;
- ; Restore the context of the program being timed and return to it.
- ;
- pop cx
- pop ax
- MPOPF
- ret
-
- ZTimerOff endp
-
- ;
- ; Called by ZTimerOff to start the timer for overhead measurements.
- ;
-
- ReferenceZTimerOn proc near
- ;
- ; Save the context of the program being timed.
- ;
- push ax
- pushf
- ;
- ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
- ; linear counting rather than count-by-two counting.
- ;
- mov al,00110100b ;mode 2
- out MODE_8253,al
- ;
- ; Set the timer count to 0.
- ;
- DELAY
- sub al,al
- out TIMER_0_8253,al ;lsb
- DELAY
- out TIMER_0_8253,al ;msb
- ;
- ; Restore the context of the program being timed and return to it.
- ;
- MPOPF
- pop ax
- ret
-
- ReferenceZTimerOn endp
-
- ;
- ; Called by ZTimerOff to stop the timer and add the result to
- ; ReferenceCount for overhead measurements. Doesn't need to look
- ; at the BIOS count because timing a zero-length code fragment
- ; isn't going to take anywhere near 54 ms.
- ;
-
- ReferenceZTimerOff proc near
- ;
- ; Save the context of the program being timed.
- ;
- pushf
- push ax
- push cx
-
- ;
- ; Match the interrupt-window delay in ZTimerOff.
- ;
- sti
- rept 10
- jmp $+2
- endm
-
- mov al,00000000b
- out MODE_8253,al ;latch timer
- ;
- ; Read the count and save it.
- ;
- DELAY
- in al,TIMER_0_8253 ;lsb
- DELAY
- mov ah,al
- in al,TIMER_0_8253 ;msb
- xchg ah,al
- neg ax ;convert from countdown
- ; remaining to elapsed
- ; count
- add cs:[ReferenceCount],ax
- ;
- ; Restore the context and return.
- ;
- pop cx
- pop ax
- MPOPF
- ret
-
- ReferenceZTimerOff endp
-
- ;********************************************************************
- ;* Routine called to report timing results. *
- ;********************************************************************
-
- ZTimerReport proc near
-
- pushf
- push ax
- push bx
- push cx
- push dx
- 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
- ;
- ; See if midnight or more than an hour passed during timing. If so,
- ; notify the user.
- ;
- mov ax,[StartBIOSCountHigh]
- cmp ax,[EndBIOSCountHigh]
- jz CalcBIOSTime ;hour count didn't change,
- ; so everything's fine
- inc ax
- cmp ax,[EndBIOSCountHigh]
- jnz TestTooLong ;midnight or two hour
- ; boundaries passed, so the
- ; results are no good
- mov ax,[EndBIOSCountLow]
- cmp ax,[StartBIOSCountLow]
- jb CalcBIOSTime ;a single hour boundary
- ; passed-that's OK, so long as
- ; the total time wasn't more
- ; than an hour
-
- ;
- ; Over an hour elapsed or midnight passed during timing, which
- ; renders the results invalid. Notify the user. This misses the
- ; case where a multiple of 24 hours has passed, but we'll rely
- ; on the perspicacity of the user to detect that case.
- ;
- TestTooLong:
- mov ah,9
- mov dx,offset TurnOverStr
- int 21h
- jmp short ZTimerReportDone
- ;
- ; Convert the BIOS time to microseconds.
- ;
- CalcBIOSTime:
- mov ax,[EndBIOSCountLow]
- sub ax,[StartBIOSCountLow]
- mov dx,54925 ;number of microseconds each
- ; BIOS count represents
- mul dx
- mov bx,ax ;set aside BIOS count in
- mov cx,dx ; microseconds
- ;
- ; Convert timer count to microseconds.
- ;
- mov ax,[EndTimedCount]
- mov si,8381
- mul si
- mov si,10000
- div si ;* .8381 = * 8381 / 10000
- ;
- ; Add timer and BIOS counts together to get an overall time in
- ; microseconds.
- ;
- add bx,ax
- adc cx,0
- ;
- ; Subtract the timer overhead and save the result.
- ;
- mov ax,[ReferenceCount]
- mov si,8381 ;convert the reference count
- mul si ; to microseconds
- mov si,10000
- div si ;* .8381 = * 8381 / 10000
- sub bx,ax
- sbb cx,0
- mov [CurrentCountLow],bx
- mov [CurrentCountHigh],cx
- ;
- ; Convert the result to an ASCII string by trial subtractions of
- ; powers of 10.
- ;
- mov di,offset PowersOfTenEnd - offset PowersOfTen - 4
- mov si,offset TimedCountStr
- CTSNextDigit:
- mov bl,'0'
- CTSLoop:
- mov ax,[CurrentCountLow]
- mov dx,[CurrentCountHigh]
- sub ax,PowersOfTen[di]
- sbb dx,PowersOfTen[di+2]
- jc CTSNextPowerDown
- inc bl
- mov [CurrentCountLow],ax
- mov [CurrentCountHigh],dx
- jmp CTSLoop
- CTSNextPowerDown:
- mov [si],bl
- inc si
- sub di,4
- jns CTSNextDigit
- ;
- ;
- ; Print the results.
- ;
- mov ah,9
- mov dx,offset OutputStr
- int 21h
- ;
- ZTimerReportDone:
- pop ds
- pop di
- pop si
- pop dx
- pop cx
- pop bx
- pop ax
- MPOPF
- ret
-
- ZTimerReport endp
-
- Code ends
- end