• Michael Abrash‘s Graphics Programming Black Book--chapter3综计器


    https://github.com/jagregory/abrash-black-book/blob/master/src/chapter-03.md
    Chapter 3 -- Assume Nothing
    Understanding and Using the Zen Timer
    shows 8253-based timer software -- 模拟环境估计没有8253,可以试试。

    LISTING 3.1 PZTIMER.ASM
    ; The precision Zen timer (PZTIMER.ASM)
    ;
    ; Uses the 8253 timer to time the performance of code that takes
    ; less than about 54 milliseconds to execute, with a resolution
    ; of better than 10 microseconds.
    ;
    ; By Michael Abrash
    ;
    ; Externally callable routines:
    ;
    ;  ZTimerOn: Starts the Zen timer, with interrupts disabled.
    ;
    ;  ZTimerOff: Stops the Zen timer, saves the timer count,
    ;    times the overhead code, and restores interrupts to the
    ;    state they were in when ZTimerOn was called.
    ;
    ;  ZTimerReport: Prints the net time that passed between starting
    ;    and stopping the timer.
    ;
    ; Note: If longer than about 54 ms passes between ZTimerOn and
    ;    ZTimerOff calls, the timer turns over and the count is
    ;    inaccurate. When this happens, an error message is displayed
    ;    instead of a count. The long-period Zen timer should be used
    ;    in such cases.
    ;
    ; Note: Interrupts *MUST* be left off between calls to ZTimerOn
    ;    and ZTimerOff for accurate timing and for detection of
    ;    timer overflow.
    ;
    ; Note: These routines can introduce slight inaccuracies into the
    ;    system clock count for each code section timed even if
    ;    timer 0 doesn't overflow. If timer 0 does overflow, the
    ;    system clock can become slow by virtually any amount of
    ;    time, since the system clock can't advance while the
    ;    precison timer is timing. Consequently, it's a good idea
    ;    to reboot at the end of each timing session. (The
    ;    battery-backed clock, if any, is not affected by the Zen
    ;    timer.)
    ;
    ; All registers, and all flags except the interrupt flag, are
    ; preserved by all routines. Interrupts are enabled and then disabled
    ; by ZTimerOn, and are restored by ZTimerOff to the state they were
    ; in when ZTimerOn was called.

    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

    LISTING 3.3 LST3-3.ASM
    执行了1000次MOV指令
    ; 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.)
    ;

    LISTING 3.4 PZTIME.BAT
    批处理来调度测量testcode

    运行:
    pztime LST3-3.ASM
    发现可以运行,说明dosbox模拟了8253外部定时器这个硬件,看来是标配。
    运行结果:
    Timed count: 00333 microseconds
    不过这个数字已经超过了54ms,按说是不对的。

    是不是用下面的长周期综计器?

    LISTING 3.5 LZTIMER.ASM
    ;
    ; 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


    LISTING 3.6 LZTEST.ASM
    ; Program to measure performance of code that takes longer than
    ; 54 ms to execute. (LZTEST.ASM)
    ;
    ; Link with LZTIMER.ASM (Listing 3.5). LZTIME.BAT (Listing 3.7)
    ; can be used to assemble and link both files. Code to be
    ; measured must be in the file TESTCODE; Listing 3.8 shows
    ; a sample file (LST3-8.ASM) which should be named TESTCODE.
    ;
    ; By Michael Abrash

    LISTING 3.7 LZTIME.BAT

    LISTING 3.8 LST3-8.ASM
    执行20000次
    ;
    ; Measures the performance of 20,000 loads of AL from
    ; memory. (Use by renaming to TESTCODE, which is
    ; included by LZTEST.ASM (Listing 3.6). LZTIME.BAT
    ; (Listing 3.7) does this, along with all assembly
    ; and linking.)
    ;
    ; Note: takes about ten minutes to assemble on a slow PC if
    ;you are using MASM

    最终看效果:
    lztime lst3-8.asm
    Timed count: 0000006669 microseconds
    是333ms的20倍。MOV次数从1000次改为20000次,正好也是20倍。所以说这个定时器还是基本准确的。
    书上的数据,在主频4.77MHZ的PC机器上执行(90年代的老机器)
    1000次:3619微妙 每次内存装入寄存器AL,约3.619微妙.我的dosbox是333微妙,差距如此之大?
    20000次:72544微妙 即内存装入寄存器AL,一次约3.63微妙,多一些是因为有中断

    PZTIMER.ASM

    1. ; The precision Zen timer (PZTIMER.ASM)
    2. ;
    3. ; Uses the 8253 timer to time the performance of code that takes
    4. ; less than about 54 milliseconds to execute, with a resolution
    5. ; of better than 10 microseconds.
    6. ;
    7. ; By Michael Abrash
    8. ;
    9. ; Externally callable routines:
    10. ;
    11. ; ZTimerOn: Starts the Zen timer, with interrupts disabled.
    12. ;
    13. ; ZTimerOff: Stops the Zen timer, saves the timer count,
    14. ; times the overhead code, and restores interrupts to the
    15. ; state they were in when ZTimerOn was called.
    16. ;
    17. ; ZTimerReport: Prints the net time that passed between starting
    18. ; and stopping the timer.
    19. ;
    20. ; Note: If longer than about 54 ms passes between ZTimerOn and
    21. ; ZTimerOff calls, the timer turns over and the count is
    22. ; inaccurate. When this happens, an error message is displayed
    23. ; instead of a count. The long-period Zen timer should be used
    24. ; in such cases.
    25. ;
    26. ; Note: Interrupts *MUST* be left off between calls to ZTimerOn
    27. ; and ZTimerOff for accurate timing and for detection of
    28. ; timer overflow.
    29. ;
    30. ; Note: These routines can introduce slight inaccuracies into the
    31. ; system clock count for each code section timed even if
    32. ; timer 0 doesn't overflow. If timer 0 does overflow, the
    33. ; system clock can become slow by virtually any amount of
    34. ; time, since the system clock can't advance while the
    35. ; precison timer is timing. Consequently, it's a good idea
    36. ; to reboot at the end of each timing session. (The
    37. ; battery-backed clock, if any, is not affected by the Zen
    38. ; timer.)
    39. ;
    40. ; All registers, and all flags except the interrupt flag, are
    41. ; preserved by all routines. Interrupts are enabled and then disabled
    42. ; by ZTimerOn, and are restored by ZTimerOff to the state they were
    43. ; in when ZTimerOn was called.
    44. ;
    45. Code segment word public 'CODE'
    46. assume cs:Code, ds:nothing
    47. public ZTimerOn, ZTimerOff, ZTimerReport
    48. ;
    49. ; Base address of the 8253 timer chip.
    50. ;
    51. BASE_8253 equ 40h
    52. ;
    53. ; The address of the timer 0 count registers in the 8253.
    54. ;
    55. TIMER_0_8253 equ BASE_8253 + 0
    56. ;
    57. ; The address of the mode register in the 8253.
    58. ;
    59. MODE_8253 equ BASE_8253 + 3
    60. ;
    61. ; The address of Operation Command Word 3 in the 8259 Programmable
    62. ; Interrupt Controller (PIC) (write only, and writable only when
    63. ; bit 4 of the byte written to this address is 0 and bit 3 is 1).
    64. ;
    65. OCW3 equ 20h
    66. ;
    67. ; The address of the Interrupt Request register in the 8259 PIC
    68. ; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0
    69. ; of OCW3 = 0).
    70. ;
    71. IRR equ 20h
    72. ;
    73. ; Macro to emulate a POPF instruction in order to fix the bug in some
    74. ; 80286 chips which allows interrupts to occur during a POPF even when
    75. ; interrupts remain disabled.
    76. ;
    77. MPOPF macro
    78. local p1, p2
    79. jmp short p2
    80. p1: iret ; jump to pushed address & pop flags
    81. p2: push cs ; construct far return address to
    82. call p1 ; the next instruction
    83. endm
    84. ;
    85. ; Macro to delay briefly to ensure that enough time has elapsed
    86. ; between successive I/O accesses so that the device being accessed
    87. ; can respond to both accesses even on a very fast PC.
    88. ;
    89. DELAY macro
    90. jmp $+2
    91. jmp $+2
    92. jmp $+2
    93. endm
    94. OriginalFlags db ? ; storage for upper byte of
    95. ; FLAGS register when
    96. ; ZTimerOn called
    97. TimedCount dw ? ; timer 0 count when the timer
    98. ; is stopped
    99. ReferenceCount dw ? ; number of counts required to
    100. ; execute timer overhead code
    101. OverflowFlag db ? ; used to indicate whether the
    102. ; timer overflowed during the
    103. ; timing interval
    104. ;
    105. ; String printed to report results.
    106. ;
    107. OutputStr label byte
    108. db 0dh, 0ah, 'Timed count: ', 5 dup (?)
    109. ASCIICountEnd label byte
    110. db ' microseconds', 0dh, 0ah
    111. db '$'
    112. ;
    113. ; String printed to report timer overflow.
    114. ;
    115. OverflowStr label byte
    116. db 0dh, 0ah
    117. db '****************************************************'
    118. db 0dh, 0ah
    119. db '* The timer overflowed, so the interval timed was *'
    120. db 0dh, 0ah
    121. db '* too long for the precision timer to measure. *'
    122. db 0dh, 0ah
    123. db '* Please perform the timing test again with the *'
    124. db 0dh, 0ah
    125. db '* long-period timer. *'
    126. db 0dh, 0ah
    127. db '****************************************************'
    128. db 0dh, 0ah
    129. db '$'
    130. ; ********************************************************************
    131. ; * Routine called to start timing. *
    132. ; ********************************************************************
    133. ZTimerOn proc near
    134. ;
    135. ; Save the context of the program being timed.
    136. ;
    137. push ax
    138. pushf
    139. pop ax ; get flags so we can keep
    140. ; interrupts off when leaving
    141. ; this routine
    142. mov cs:[OriginalFlags],ah ; remember the state of the
    143. ; Interrupt flag
    144. and ah,0fdh ; set pushed interrupt flag
    145. ; to 0
    146. push ax
    147. ;
    148. ; Turn on interrupts, so the timer interrupt can occur if it's
    149. ; pending.
    150. ;
    151. sti
    152. ;
    153. ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
    154. ; linear counting rather than count-by-two counting. Also
    155. ; leaves the 8253 waiting for the initial timer 0 count to
    156. ; be loaded.
    157. ;
    158. mov al,00110100b ;mode 2
    159. out MODE_8253,al
    160. ;
    161. ; Set the timer count to 0, so we know we won't get another
    162. ; timer interrupt right away.
    163. ; Note: this introduces an inaccuracy of up to 54 ms in the system
    164. ; clock count each time it is executed.
    165. ;
    166. DELAY
    167. sub al,al
    168. out TIMER_0_8253,al ;lsb
    169. DELAY
    170. out TIMER_0_8253,al ;msb
    171. ;
    172. ; Wait before clearing interrupts to allow the interrupt generated
    173. ; when switching from mode 3 to mode 2 to be recognized. The delay
    174. ; must be at least 210 ns long to allow time for that interrupt to
    175. ; occur. Here, 10 jumps are used for the delay to ensure that the
    176. ; delay time will be more than long enough even on a very fast PC.
    177. ;
    178. rept 10
    179. jmp $+2
    180. endm
    181. ;
    182. ; Disable interrupts to get an accurate count.
    183. ;
    184. cli
    185. ;
    186. ; Set the timer count to 0 again to start the timing interval.
    187. ;
    188. mov al,00110100b ; set up to load initial
    189. out MODE_8253,al ; timer count
    190. DELAY
    191. sub al,al
    192. out TIMER_0_8253,al ; load count lsb
    193. DELAY
    194. out TIMER_0_8253,al; load count msb
    195. ;
    196. ; Restore the context and return.
    197. ;
    198. MPOPF ; keeps interrupts off
    199. pop ax
    200. ret
    201. ZTimerOn endp
    202. ;********************************************************************
    203. ;* Routine called to stop timing and get count. *
    204. ;********************************************************************
    205. ZTimerOff proc near
    206. ;
    207. ; Save the context of the program being timed.
    208. ;
    209. push ax
    210. push cx
    211. pushf
    212. ;
    213. ; Latch the count.
    214. ;
    215. mov al,00000000b ; latch timer 0
    216. out MODE_8253,al
    217. ;
    218. ; See if the timer has overflowed by checking the 8259 for a pending
    219. ; timer interrupt.
    220. ;
    221. mov al,00001010b ; OCW3, set up to read
    222. out OCW3,al ; Interrupt Request register
    223. DELAY
    224. in al,IRR ; read Interrupt Request
    225. ; register
    226. and al,1 ; set AL to 1 if IRQ0 (the
    227. ; timer interrupt) is pending
    228. mov cs:[OverflowFlag],al; store the timer overflow
    229. ; status
    230. ;
    231. ; Allow interrupts to happen again.
    232. ;
    233. sti
    234. ;
    235. ; Read out the count we latched earlier.
    236. ;
    237. in al,TIMER_0_8253 ; least significant byte
    238. DELAY
    239. mov ah,al
    240. in al,TIMER_0_8253 ; most significant byte
    241. xchg ah,al
    242. neg ax ; convert from countdown
    243. ; remaining to elapsed
    244. ; count
    245. mov cs:[TimedCount],ax
    246. ; Time a zero-length code fragment, to get a reference for how
    247. ; much overhead this routine has. Time it 16 times and average it,
    248. ; for accuracy, rounding the result.
    249. ;
    250. mov cs:[ReferenceCount],0
    251. mov cx,16
    252. cli ; interrupts off to allow a
    253. ; precise reference count
    254. RefLoop:
    255. call ReferenceZTimerOn
    256. call ReferenceZTimerOff
    257. loop RefLoop
    258. sti
    259. add cs:[ReferenceCount],8; total + (0.5 * 16)
    260. mov cl,4
    261. shr cs:[ReferenceCount],cl; (total) / 16 + 0.5
    262. ;
    263. ; Restore original interrupt state.
    264. ;
    265. pop ax ; retrieve flags when called
    266. mov ch,cs:[OriginalFlags] ; get back the original upper
    267. ; byte of the FLAGS register
    268. and ch,not 0fdh ; only care about original
    269. ; interrupt flag...
    270. and ah,0fdh ; ...keep all other flags in
    271. ; their current condition
    272. or ah,ch ; make flags word with original
    273. ; interrupt flag
    274. push ax ; prepare flags to be popped
    275. ;
    276. ; Restore the context of the program being timed and return to it.
    277. ;
    278. MPOPF ; restore the flags with the
    279. ; original interrupt state
    280. pop cx
    281. pop ax
    282. ret
    283. ZTimerOff endp
    284. ;
    285. ; Called by ZTimerOff to start timer for overhead measurements.
    286. ;
    287. ReferenceZTimerOn proc near
    288. ;
    289. ; Save the context of the program being timed.
    290. ;
    291. push ax
    292. pushf ; interrupts are already off
    293. ;
    294. ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
    295. ; linear counting rather than count-by-two counting.
    296. ;
    297. mov al,00110100b ; set up to load
    298. out MODE_8253,al ; initial timer count
    299. DELAY
    300. ;
    301. ; Set the timer count to 0.
    302. ;
    303. sub al,al
    304. out TIMER_0_8253,al; load count lsb
    305. DELAY
    306. out TIMER_0_8253,al; load count msb
    307. ;
    308. ; Restore the context of the program being timed and return to it.
    309. ;
    310. MPOPF
    311. pop ax
    312. ret
    313. ReferenceZTimerOn endp
    314. ;
    315. ; Called by ZTimerOff to stop timer and add result to ReferenceCount
    316. ; for overhead measurements.
    317. ;
    318. ReferenceZTimerOff proc near
    319. ;
    320. ; Save the context of the program being timed.
    321. ;
    322. push ax
    323. push cx
    324. pushf
    325. ;
    326. ; Latch the count and read it.
    327. ;
    328. mov al,00000000b ; latch timer 0
    329. out MODE_8253,al
    330. DELAY
    331. in al,TIMER_0_8253 ; lsb
    332. DELAY
    333. mov ah,al
    334. in al,TIMER_0_8253 ; msb
    335. xchg ah,al
    336. neg ax ; convert from countdown
    337. ; remaining to amount
    338. ; counted down
    339. add cs:[ReferenceCount],ax
    340. ;
    341. ; Restore the context of the program being timed and return to it.
    342. ;
    343. MPOPF
    344. pop cx
    345. pop ax
    346. ret
    347. ReferenceZTimerOff endp
    348. ; ********************************************************************
    349. ; * Routine called to report timing results. *
    350. ; ********************************************************************
    351. ZTimerReport proc near
    352. pushf
    353. push ax
    354. push bx
    355. push cx
    356. push dx
    357. push si
    358. push ds
    359. ;
    360. push cs ; DOS functions require that DS point
    361. pop ds ; to text to be displayed on the screen
    362. assume ds :Code
    363. ;
    364. ; Check for timer 0 overflow.
    365. ;
    366. cmp [OverflowFlag],0
    367. jz PrintGoodCount
    368. mov dx,offset OverflowStr
    369. mov ah,9
    370. int 21h
    371. jmp short EndZTimerReport
    372. ;
    373. ; Convert net count to decimal ASCII in microseconds.
    374. ;
    375. PrintGoodCount:
    376. mov ax,[TimedCount]
    377. sub ax,[ReferenceCount]
    378. mov si,offset ASCIICountEnd - 1
    379. ;
    380. ; Convert count to microseconds by multiplying by .8381.
    381. ;
    382. mov dx, 8381
    383. mul dx
    384. mov bx, 10000
    385. div bx ;* .8381 = * 8381 / 10000
    386. ;
    387. ; Convert time in microseconds to 5 decimal ASCII digits.
    388. ;
    389. mov bx, 10
    390. mov cx, 5
    391. CTSLoop:
    392. sub dx, dx
    393. div bx
    394. add dl,'0'
    395. mov [si],dl
    396. dec si
    397. loop CTSLoop
    398. ;
    399. ; Print the results.
    400. ;
    401. mov ah, 9
    402. mov dx, offset OutputStr
    403. int 21h
    404. ;
    405. EndZTimerReport:
    406. pop ds
    407. pop si
    408. pop dx
    409. pop cx
    410. pop bx
    411. pop ax
    412. MPOPF
    413. ret
    414. ZTimerReport endp
    415. Code ends
    416. end

    PZTEST.ASM

    1. ; Program to measure performance of code that takes less than
    2. ; 54 ms to execute. (PZTEST.ASM)
    3. ;
    4. ; Link with PZTIMER.ASM (Listing 3.1). PZTEST.BAT (Listing 3.4)
    5. ; can be used to assemble and link both files. Code to be
    6. ; measured must be in the file TESTCODE; Listing 3.3 shows
    7. ; a sample TESTCODE file.
    8. ;
    9. ; By Michael Abrash
    10. ;
    11. mystack segment para stack 'STACK'
    12. db 512 dup(?)
    13. mystack ends
    14. ;
    15. Code segment para public 'CODE'
    16. assume cs:Code, ds:Code
    17. extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near
    18. Start proc near
    19. push cs
    20. pop ds ; set DS to point to the code segment,
    21. ; so data as well as code can easily
    22. ; be included in TESTCODE
    23. ;
    24. include TESTCODE ;code to be measured, including
    25. ; calls to ZTimerOn and ZTimerOff
    26. ;
    27. ; Display the results.
    28. ;
    29. call ZTimerReport
    30. ;
    31. ; Terminate the program.
    32. ;
    33. mov ah,4ch
    34. int 21h
    35. Start endp
    36. Code ends
    37. end Start

    LST3-3.ASM

    1. ; Test file;
    2. ; Measures the performance of 1,000 loads of AL from
    3. ; memory. (Use by renaming to TESTCODE, which is
    4. ; included by PZTEST.ASM (Listing 3.2). PZTIME.BAT
    5. ; (Listing 3.4) does this, along with all assembly
    6. ; and linking.)
    7. ;
    8. jmp Skip ;jump around defined data
    9. ;
    10. MemVar db ?
    11. ;
    12. Skip:
    13. ;
    14. ; Start timing.
    15. ;
    16. call ZTimerOn
    17. ;
    18. rept 1000
    19. mov al,[MemVar]
    20. endm
    21. ;
    22. ; Stop timing.
    23. ;
    24. call ZTimerOff

    PZTIME.BAT

    1. echo off
    2. rem
    3. rem *** Listing 3.4 ***
    4. rem
    5. rem ***************************************************************
    6. rem * Batch file PZTIME.BAT, which builds and runs the precision *
    7. rem * Zen timer program PZTEST.EXE to time the code named as the *
    8. rem * command-line parameter. Listing 3.1 must be named *
    9. rem * PZTIMER.ASM, and Listing 3.2 must be named PZTEST.ASM. To *
    10. rem * time the code in LST3-3, you'd type the DOS command: *
    11. rem * *
    12. rem * pztime lst3-3 *
    13. rem * *
    14. rem * Note that MASM and LINK must be in the current directory or *
    15. rem * on the current path in order for this batch file to work. *
    16. rem * *
    17. rem * This batch file can be speeded up by assembling PZTIMER.ASM *
    18. rem * once, then removing the lines: *
    19. rem * *
    20. rem * masm pztimer; *
    21. rem * if errorlevel 1 goto errorend *
    22. rem * *
    23. rem * from this file. *
    24. rem * *
    25. rem * By Michael Abrash *
    26. rem ***************************************************************
    27. rem
    28. rem Make sure a file to test was specified.
    29. rem
    30. if not x%1==x goto ckexist
    31. echo ***************************************************************
    32. echo * Please specify a file to test. *
    33. echo ***************************************************************
    34. goto end
    35. rem
    36. rem Make sure the file exists.
    37. rem
    38. :ckexist
    39. if exist %1 goto docopy
    40. echo ***************************************************************
    41. echo * The specified file, "%1," doesn't exist. *
    42. echo ***************************************************************
    43. goto end
    44. rem
    45. rem copy the file to measure to TESTCODE.
    46. rem
    47. :docopy
    48. copy %1 testcode
    49. masm pztest;
    50. if errorlevel 1 goto errorend
    51. masm pztimer;
    52. if errorlevel 1 goto errorend
    53. link pztest+pztimer;
    54. if errorlevel 1 goto errorend
    55. pztest
    56. goto end
    57. :errorend
    58. echo ***************************************************************
    59. echo * An error occurred while building the precision Zen timer. *
    60. echo ***************************************************************
    61. :end

    LZTIMER.ASM

    1. ;
    2. ; The long-period Zen timer. (LZTIMER.ASM)
    3. ; Uses the 8253 timer and the BIOS time-of-day count to time the
    4. ; performance of code that takes less than an hour to execute.
    5. ; Because interrupts are left on (in order to allow the timer
    6. ; interrupt to be recognized), this is less accurate than the
    7. ; precision Zen timer, so it is best used only to time code that takes
    8. ; more than about 54 milliseconds to execute (code that the precision
    9. ; Zen timer reports overflow on). Resolution is limited by the
    10. ; occurrence of timer interrupts.
    11. ;
    12. ; By Michael Abrash
    13. ;
    14. ; Externally callable routines:
    15. ;
    16. ; ZTimerOn: Saves the BIOS time of day count and starts the
    17. ; long-period Zen timer.
    18. ;
    19. ; ZTimerOff: Stops the long-period Zen timer and saves the timer
    20. ; count and the BIOS time-of-day count.
    21. ;
    22. ; ZTimerReport: Prints the time that passed between starting and
    23. ; stopping the timer.
    24. ;
    25. ; Note: If either more than an hour passes or midnight falls between
    26. ; calls to ZTimerOn and ZTimerOff, an error is reported. For
    27. ; timing code that takes more than a few minutes to execute,
    28. ; either the DOS TIME command in a batch file before and after
    29. ; execution of the code to time or the use of the DOS
    30. ; time-of-day function in place of the long-period Zen timer is
    31. ; more than adequate.
    32. ;
    33. ; Note: The PS/2 version is assembled by setting the symbol PS2 to 1.
    34. ; PS2 must be set to 1 on PS/2 computers because the PS/2's
    35. ; timers are not compatible with an undocumented timer-stopping
    36. ; feature of the 8253; the alternative timing approach that
    37. ; must be used on PS/2 computers leaves a short window
    38. ; during which the timer 0 count and the BIOS timer count may
    39. ; not be synchronized. You should also set the PS2 symbol to
    40. ; 1 if you're getting erratic or obviously incorrect results.
    41. ;
    42. ; Note: When PS2 is 0, the code relies on an undocumented 8253
    43. ; feature to get more reliable readings. It is possible that
    44. ; the 8253 (or whatever chip is emulating the 8253) may be put
    45. ; into an undefined or incorrect state when this feature is
    46. ; used.
    47. ;
    48. ; ******************************************************************
    49. ; * If your computer displays any hint of erratic behavior *
    50. ; * after the long-period Zen timer is used, such as the floppy*
    51. ; * drive failing to operate properly, reboot the system, set *
    52. ; * PS2 to 1 and leave it that way! *
    53. ; ******************************************************************
    54. ;
    55. ; Note: Each block of code being timed should ideally be run several
    56. ; times, with at least two similar readings required to
    57. ; establish a true measurement, in order to eliminate any
    58. ; variability caused by interrupts.
    59. ;
    60. ; Note: Interrupts must not be disabled for more than 54 ms at a
    61. ; stretch during the timing interval. Because interrupts
    62. ; are enabled, keys, mice, and other devices that generate
    63. ; interrupts should not be used during the timing interval.
    64. ;
    65. ; Note: Any extra code running off the timer interrupt (such as
    66. ; some memory-resident utilities) will increase the time
    67. ; measured by the Zen timer.
    68. ;
    69. ; Note: These routines can introduce inaccuracies of up to a few
    70. ; tenths of a second into the system clock count for each
    71. ; code section timed. Consequently, it's a good idea to
    72. ; reboot at the conclusion of timing sessions. (The
    73. ; battery-backed clock, if any, is not affected by the Zen
    74. ; timer.)
    75. ;
    76. ; All registers and all flags are preserved by all routines.
    77. ;
    78. Code segment word public 'CODE'
    79. assume cs:Code, ds:nothing
    80. public ZTimerOn, ZTimerOff, ZTimerReport
    81. ;
    82. ; Set PS2 to 0 to assemble for use on a fully 8253-compatible
    83. ; system; when PS2 is 0, the readings are more reliable if the
    84. ; computer supports the undocumented timer-stopping feature,
    85. ; but may be badly off if that feature is not supported. In
    86. ; fact, timer-stopping may interfere with your computer's
    87. ; overall operation by putting the 8253 into an undefined or
    88. ; incorrect state. Use with caution!!!
    89. ;
    90. ; Set PS2 to 1 to assemble for use on non-8253-compatible
    91. ; systems, including PS/2 computers; when PS2 is 1, readings
    92. ; may occasionally be off by 54 ms, but the code will work
    93. ; properly on all systems.
    94. ;
    95. ; A setting of 1 is safer and will work on more systems,
    96. ; while a setting of 0 produces more reliable results in systems
    97. ; which support the undocumented timer-stopping feature of the
    98. ; 8253. The choice is yours.
    99. ;
    100. PS2 equ 1
    101. ;
    102. ; Base address of the 8253 timer chip.
    103. ;
    104. BASE_8253 equ 40h
    105. ;
    106. ; The address of the timer 0 count registers in the 8253.
    107. ;
    108. TIMER_0_8253 equ BASE_8253 + 0
    109. ;
    110. ; The address of the mode register in the 8253.
    111. ;
    112. MODE_8253 equ BASE_8253 + 3
    113. ;
    114. ; The address of the BIOS timer count variable in the BIOS
    115. ; data segment.
    116. ;
    117. TIMER_COUNT equ 46ch
    118. ;
    119. ; Macro to emulate a POPF instruction in order to fix the bug in some
    120. ; 80286 chips which allows interrupts to occur during a POPF even when
    121. ; interrupts remain disabled.
    122. ;
    123. MPOPF macro
    124. local p1, p2
    125. jmp short p2
    126. p1: iret ;jump to pushed address & pop flags
    127. p2: push cs ;construct far return address to
    128. call p1 ; the next instruction
    129. endm
    130. ;
    131. ; Macro to delay briefly to ensure that enough time has elapsed
    132. ; between successive I/O accesses so that the device being accessed
    133. ; can respond to both accesses even on a very fast PC.
    134. ;
    135. DELAY macro
    136. jmp $+2
    137. jmp $+2
    138. jmp $+2
    139. endm
    140. StartBIOSCountLow dw ? ;BIOS count low word at the
    141. ; start of the timing period
    142. StartBIOSCountHigh dw ? ;BIOS count high word at the
    143. ; start of the timing period
    144. EndBIOSCountLow dw ? ;BIOS count low word at the
    145. ; end of the timing period
    146. EndBIOSCountHigh dw ? ;BIOS count high word at the
    147. ; end of the timing period
    148. EndTimedCount dw ? ;timer 0 count at the end of
    149. ; the timing period
    150. ReferenceCount dw ? ;number of counts required to
    151. ; execute timer overhead code
    152. ;
    153. ; String printed to report results.
    154. ;
    155. OutputStr label byte
    156. db 0dh, 0ah, 'Timed count: '
    157. TimedCountStr db 10 dup (?)
    158. db ' microseconds', 0dh, 0ah
    159. db '$'
    160. ;
    161. ; Temporary storage for timed count as it's divided down by powers
    162. ; of ten when converting from doubleword binary to ASCII.
    163. ;
    164. CurrentCountLow dw ?
    165. CurrentCountHigh dw ?
    166. ;
    167. ; Powers of ten table used to perform division by 10 when doing
    168. ; doubleword conversion from binary to ASCII.
    169. ;
    170. PowersOfTen label word
    171. dd 1
    172. dd 10
    173. dd 100
    174. dd 1000
    175. dd 10000
    176. dd 100000
    177. dd 1000000
    178. dd 10000000
    179. dd 100000000
    180. dd 1000000000
    181. PowersOfTenEnd label word
    182. ;
    183. ; String printed to report that the high word of the BIOS count
    184. ; changed while timing (an hour elapsed or midnight was crossed),
    185. ; and so the count is invalid and the test needs to be rerun.
    186. ;
    187. TurnOverStr label byte
    188. db 0dh, 0ah
    189. db '****************************************************'
    190. db 0dh, 0ah
    191. db '* Either midnight passed or an hour or more passed *'
    192. db 0dh, 0ah
    193. db '* while timing was in progress. If the former was *'
    194. db 0dh, 0ah
    195. db '* the case, please rerun the test; if the latter *'
    196. db 0dh, 0ah
    197. db '* was the case, the test code takes too long to *'
    198. db 0dh, 0ah
    199. db '* run to be timed by the long-period Zen timer. *'
    200. db 0dh, 0ah
    201. db '* Suggestions: use the DOS TIME command, the DOS *'
    202. db 0dh, 0ah
    203. db '* time function, or a watch. *'
    204. db 0dh, 0ah
    205. db '****************************************************'
    206. db 0dh, 0ah
    207. db '$'
    208. ;********************************************************************
    209. ;* Routine called to start timing. *
    210. ;********************************************************************
    211. ZTimerOn proc near
    212. ;
    213. ; Save the context of the program being timed.
    214. ;
    215. push ax
    216. pushf
    217. ;
    218. ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
    219. ; linear counting rather than count-by-two counting. Also stops
    220. ; timer 0 until the timer count is loaded, except on PS/2
    221. ; computers.
    222. ;
    223. mov al,00110100b ;mode 2
    224. out MODE_8253,al
    225. ;
    226. ; Set the timer count to 0, so we know we won't get another
    227. ; timer interrupt right away.
    228. ; Note: this introduces an inaccuracy of up to 54 ms in the system
    229. ; clock count each time it is executed.
    230. ;
    231. DELAY
    232. sub al,al
    233. out TIMER_0_8253,al ;lsb
    234. DELAY
    235. out TIMER_0_8253,al ;msb
    236. ;
    237. ; In case interrupts are disabled, enable interrupts briefly to allow
    238. ; the interrupt generated when switching from mode 3 to mode 2 to be
    239. ; recognized. Interrupts must be enabled for at least 210 ns to allow
    240. ; time for that interrupt to occur. Here, 10 jumps are used for the
    241. ; delay to ensure that the delay time will be more than long enough
    242. ; even on a very fast PC.
    243. ;
    244. pushf
    245. sti
    246. rept 10
    247. jmp $+2
    248. endm
    249. MPOPF
    250. ;
    251. ; Store the timing start BIOS count.
    252. ; (Since the timer count was just set to 0, the BIOS count will
    253. ; stay the same for the next 54 ms, so we don't need to disable
    254. ; interrupts in order to avoid getting a half-changed count.)
    255. ;
    256. push ds
    257. sub ax, ax
    258. mov ds, ax
    259. mov ax, ds:[TIMER_COUNT+2]
    260. mov cs:[StartBIOSCountHigh],ax
    261. mov ax, ds:[TIMER_COUNT]
    262. mov cs:[StartBIOSCountLow],ax
    263. pop ds
    264. ;
    265. ; Set the timer count to 0 again to start the timing interval.
    266. ;
    267. mov al,00110100b ;set up to load initial
    268. out MODE_8253,al ; timer count
    269. DELAY
    270. sub al, al
    271. out TIMER_0_8253,al; load count lsb
    272. DELAY
    273. out TIMER_0_8253,al; load count msb
    274. ;
    275. ; Restore the context of the program being timed and return to it.
    276. ;
    277. MPOPF
    278. pop ax
    279. ret
    280. ZTimerOn endp
    281. ;********************************************************************
    282. ;* Routine called to stop timing and get count. *
    283. ;********************************************************************
    284. ZTimerOff proc near
    285. ;
    286. ; Save the context of the program being timed.
    287. ;
    288. pushf
    289. push ax
    290. push cx
    291. ;
    292. ; In case interrupts are disabled, enable interrupts briefly to allow
    293. ; any pending timer interrupt to be handled. Interrupts must be
    294. ; enabled for at least 210 ns to allow time for that interrupt to
    295. ; occur. Here, 10 jumps are used for the delay to ensure that the
    296. ; delay time will be more than long enough even on a very fast PC.
    297. ;
    298. sti
    299. rept 10
    300. jmp $+2
    301. endm
    302. ;
    303. ; Latch the timer count.
    304. ;
    305. if PS2
    306. mov al,00000000b
    307. out MODE_8253,al ;latch timer 0 count
    308. ;
    309. ; This is where a one-instruction-long window exists on the PS/2.
    310. ; The timer count and the BIOS count can lose synchronization;
    311. ; since the timer keeps counting after it's latched, it can turn
    312. ; over right after it's latched and cause the BIOS count to turn
    313. ; over before interrupts are disabled, leaving us with the timer
    314. ; count from before the timer turned over coupled with the BIOS
    315. ; count from after the timer turned over. The result is a count
    316. ; that's 54 ms too long.
    317. ;
    318. else
    319. ;
    320. ; Set timer 0 to mode 2 (divide-by-N), waiting for a 2-byte count
    321. ; load, which stops timer 0 until the count is loaded. (Only works
    322. ; on fully 8253-compatible chips.)
    323. ;
    324. mov al,00110100b; mode 2
    325. out MODE_8253,al
    326. DELAY
    327. mov al,00000000b ;latch timer 0 count
    328. out MODE_8253,al
    329. endif
    330. cli ;stop the BIOS count
    331. ;
    332. ; Read the BIOS count. (Since interrupts are disabled, the BIOS
    333. ; count won't change.)
    334. ;
    335. push ds
    336. sub ax,ax
    337. mov ds,ax
    338. mov ax,ds:[TIMER_COUNT+2]
    339. mov cs:[EndBIOSCountHigh],ax
    340. mov ax,ds:[TIMER_COUNT]
    341. mov cs:[EndBIOSCountLow],ax
    342. pop ds
    343. ;
    344. ; Read the timer count and save it.
    345. ;
    346. in al,TIMER_0_8253 ;lsb
    347. DELAY
    348. mov ah,al
    349. in al,TIMER_0_8253 ;msb
    350. xchg ah,al
    351. neg ax ;convert from countdown
    352. ; remaining to elapsed
    353. ; count
    354. mov cs:[EndTimedCount],ax
    355. ;
    356. ; Restart timer 0, which is still waiting for an initial count
    357. ; to be loaded.
    358. ;
    359. ife PS2
    360. DELAY
    361. mov al,00110100b ;mode 2, waiting to load a
    362. ; 2-byte count
    363. out MODE_8253,al
    364. DELAY
    365. sub al,al
    366. out TIMER_0_8253,al ;lsb
    367. DELAY
    368. mov al,ah
    369. out TIMER_0_8253,al ;msb
    370. DELAY
    371. endif
    372. sti ;let the BIOS count continue
    373. ;
    374. ; Time a zero-length code fragment, to get a reference for how
    375. ; much overhead this routine has. Time it 16 times and average it,
    376. ; for accuracy, rounding the result.
    377. ;
    378. mov cs:[ReferenceCount],0
    379. mov cx,16
    380. cli ;interrupts off to allow a
    381. ; precise reference count
    382. RefLoop:
    383. call ReferenceZTimerOn
    384. call ReferenceZTimerOff
    385. loop RefLoop
    386. sti
    387. add cs:[ReferenceCount],8 ;total + (0.5 * 16)
    388. mov cl,4
    389. shr cs:[ReferenceCount],cl ;(total) / 16 + 0.5
    390. ;
    391. ; Restore the context of the program being timed and return to it.
    392. ;
    393. pop cx
    394. pop ax
    395. MPOPF
    396. ret
    397. ZTimerOff endp
    398. ;
    399. ; Called by ZTimerOff to start the timer for overhead measurements.
    400. ;
    401. ReferenceZTimerOn proc near
    402. ;
    403. ; Save the context of the program being timed.
    404. ;
    405. push ax
    406. pushf
    407. ;
    408. ; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause
    409. ; linear counting rather than count-by-two counting.
    410. ;
    411. mov al,00110100b ;mode 2
    412. out MODE_8253,al
    413. ;
    414. ; Set the timer count to 0.
    415. ;
    416. DELAY
    417. sub al,al
    418. out TIMER_0_8253,al ;lsb
    419. DELAY
    420. out TIMER_0_8253,al ;msb
    421. ;
    422. ; Restore the context of the program being timed and return to it.
    423. ;
    424. MPOPF
    425. pop ax
    426. ret
    427. ReferenceZTimerOn endp
    428. ;
    429. ; Called by ZTimerOff to stop the timer and add the result to
    430. ; ReferenceCount for overhead measurements. Doesn't need to look
    431. ; at the BIOS count because timing a zero-length code fragment
    432. ; isn't going to take anywhere near 54 ms.
    433. ;
    434. ReferenceZTimerOff proc near
    435. ;
    436. ; Save the context of the program being timed.
    437. ;
    438. pushf
    439. push ax
    440. push cx
    441. ;
    442. ; Match the interrupt-window delay in ZTimerOff.
    443. ;
    444. sti
    445. rept 10
    446. jmp $+2
    447. endm
    448. mov al,00000000b
    449. out MODE_8253,al ;latch timer
    450. ;
    451. ; Read the count and save it.
    452. ;
    453. DELAY
    454. in al,TIMER_0_8253 ;lsb
    455. DELAY
    456. mov ah,al
    457. in al,TIMER_0_8253 ;msb
    458. xchg ah,al
    459. neg ax ;convert from countdown
    460. ; remaining to elapsed
    461. ; count
    462. add cs:[ReferenceCount],ax
    463. ;
    464. ; Restore the context and return.
    465. ;
    466. pop cx
    467. pop ax
    468. MPOPF
    469. ret
    470. ReferenceZTimerOff endp
    471. ;********************************************************************
    472. ;* Routine called to report timing results. *
    473. ;********************************************************************
    474. ZTimerReport proc near
    475. pushf
    476. push ax
    477. push bx
    478. push cx
    479. push dx
    480. push si
    481. push di
    482. push ds
    483. ;
    484. push cs ;DOS functions require that DS point
    485. pop ds ; to text to be displayed on the screen
    486. assume ds :Code
    487. ;
    488. ; See if midnight or more than an hour passed during timing. If so,
    489. ; notify the user.
    490. ;
    491. mov ax,[StartBIOSCountHigh]
    492. cmp ax,[EndBIOSCountHigh]
    493. jz CalcBIOSTime ;hour count didn't change,
    494. ; so everything's fine
    495. inc ax
    496. cmp ax,[EndBIOSCountHigh]
    497. jnz TestTooLong ;midnight or two hour
    498. ; boundaries passed, so the
    499. ; results are no good
    500. mov ax,[EndBIOSCountLow]
    501. cmp ax,[StartBIOSCountLow]
    502. jb CalcBIOSTime ;a single hour boundary
    503. ; passed--that's OK, so long as
    504. ; the total time wasn't more
    505. ; than an hour
    506. ;
    507. ; Over an hour elapsed or midnight passed during timing, which
    508. ; renders the results invalid. Notify the user. This misses the
    509. ; case where a multiple of 24 hours has passed, but we'll rely
    510. ; on the perspicacity of the user to detect that case.
    511. ;
    512. TestTooLong:
    513. mov ah,9
    514. mov dx,offset TurnOverStr
    515. int 21h
    516. jmp short ZTimerReportDone
    517. ;
    518. ; Convert the BIOS time to microseconds.
    519. ;
    520. CalcBIOSTime:
    521. mov ax,[EndBIOSCountLow]
    522. sub ax,[StartBIOSCountLow]
    523. mov dx,54925 ;number of microseconds each
    524. ; BIOS count represents
    525. mul dx
    526. mov bx,ax ;set aside BIOS count in
    527. mov cx,dx ; microseconds
    528. ;
    529. ; Convert timer count to microseconds.
    530. ;
    531. mov ax,[EndTimedCount]
    532. mov si,8381
    533. mul si
    534. mov si,10000
    535. div si ;* .8381 = * 8381 / 10000
    536. ;
    537. ; Add timer and BIOS counts together to get an overall time in
    538. ; microseconds.
    539. ;
    540. add bx,ax
    541. adc cx,0
    542. ;
    543. ; Subtract the timer overhead and save the result.
    544. ;
    545. mov ax,[ReferenceCount]
    546. mov si,8381 ;convert the reference count
    547. mul si ; to microseconds
    548. mov si,10000
    549. div si ;* .8381 = * 8381 / 10000
    550. sub bx,ax
    551. sbb cx,0
    552. mov [CurrentCountLow],bx
    553. mov [CurrentCountHigh],cx
    554. ;
    555. ; Convert the result to an ASCII string by trial subtractions of
    556. ; powers of 10.
    557. ;
    558. mov di,offset PowersOfTenEnd - offset PowersOfTen - 4
    559. mov si,offset TimedCountStr
    560. CTSNextDigit:
    561. mov bl,'0'
    562. CTSLoop:
    563. mov ax,[CurrentCountLow]
    564. mov dx,[CurrentCountHigh]
    565. sub ax,PowersOfTen[di]
    566. sbb dx,PowersOfTen[di+2]
    567. jc CTSNextPowerDown
    568. inc bl
    569. mov [CurrentCountLow],ax
    570. mov [CurrentCountHigh],dx
    571. jmp CTSLoop
    572. CTSNextPowerDown:
    573. mov [si],bl
    574. inc si
    575. sub di,4
    576. jns CTSNextDigit
    577. ;
    578. ;
    579. ; Print the results.
    580. ;
    581. mov ah,9
    582. mov dx,offset OutputStr
    583. int 21h
    584. ;
    585. ZTimerReportDone:
    586. pop ds
    587. pop di
    588. pop si
    589. pop dx
    590. pop cx
    591. pop bx
    592. pop ax
    593. MPOPF
    594. ret
    595. ZTimerReport endp
    596. Code ends
    597. end

    LZTEST.ASM

    1. ; Program to measure performance of code that takes longer than
    2. ; 54 ms to execute. (LZTEST.ASM)
    3. ;
    4. ; Link with LZTIMER.ASM (Listing 3.5). LZTIME.BAT (Listing 3.7)
    5. ; can be used to assemble and link both files. Code to be
    6. ; measured must be in the file TESTCODE; Listing 3.8 shows
    7. ; a sample file (LST3-8.ASM) which should be named TESTCODE.
    8. ;
    9. ; By Michael Abrash
    10. ;
    11. mystack segment para stack 'STACK'
    12. db 512 dup(?)
    13. mystack ends
    14. ;
    15. Code segment para public 'CODE'
    16. assume cs:Code, ds:Code
    17. extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near
    18. Start proc near
    19. push cs
    20. pop ds ;point DS to the code segment,
    21. ; so data as well as code can easily
    22. ; be included in TESTCODE
    23. ;
    24. ; Delay for 6-7 seconds, to let the Enter keystroke that started the
    25. ; program come back up.
    26. ;
    27. mov ah,2ch
    28. int 21h ;get the current time
    29. mov bh,dh ;set the current time aside
    30. DelayLoop:
    31. mov ah,2ch
    32. push bx ;preserve start time
    33. int 21h ;get time
    34. pop bx ;retrieve start time
    35. cmp dh,bh ;is the new seconds count less than
    36. ; the start seconds count?
    37. jnb CheckDelayTime ;no
    38. add dh,60 ;yes, a minute must have turned over,
    39. ; so add one minute
    40. CheckDelayTime:
    41. sub dh,bh ;get time that's passed
    42. cmp dh,7 ;has it been more than 6 seconds yet?
    43. jb DelayLoop ;not yet
    44. ;
    45. include TESTCODE ;code to be measured, including calls
    46. ; to ZTimerOn and ZTimerOff
    47. ;
    48. ; Display the results.
    49. ;
    50. call ZTimerReport
    51. ;
    52. ; Terminate the program.
    53. ;
    54. mov ah,4ch
    55. int 21h
    56. Start endp
    57. Code ends
    58. end Start

    LST3-8.ASM

    1. ;
    2. ; Measures the performance of 20,000 loads of AL from
    3. ; memory. (Use by renaming to TESTCODE, which is
    4. ; included by LZTEST.ASM (Listing 3.6). LZTIME.BAT
    5. ; (Listing 3.7) does this, along with all assembly
    6. ; and linking.)
    7. ;
    8. ; Note: takes about ten minutes to assemble on a slow PC if
    9. ;you are using MASM
    10. ;
    11. jmp Skip ;jump around defined data
    12. ;
    13. MemVar db ?
    14. ;
    15. Skip:
    16. ;
    17. ; Start timing.
    18. ;
    19. call ZTimerOn
    20. ;
    21. rept 20000
    22. mov al,[MemVar]
    23. endm
    24. ;
    25. ; Stop timing.
    26. ;
    27. call ZTimerOff

    LZTIME.BAT

    1. echo off
    2. rem
    3. rem *** Listing 3.7 ***
    4. rem
    5. rem ***************************************************************
    6. rem * Batch file LZTIME.BAT, which builds and runs the *
    7. rem * long-period Zen timer program LZTEST.EXE to time the code *
    8. rem * named as the command-line parameter. Listing 3.5 must be *
    9. rem * named LZTIMER.ASM, and Listing 3.6 must be named *
    10. rem * LZTEST.ASM. To time the code in LST3-8, you'd type the *
    11. rem * DOS command: *
    12. rem * *
    13. rem * lztime lst3-8 *
    14. rem * *
    15. rem * Note that MASM and LINK must be in the current directory or *
    16. rem * on the current path in order for this batch file to work. *
    17. rem * *
    18. rem * This batch file can be speeded up by assembling LZTIMER.ASM *
    19. rem * once, then removing the lines: *
    20. rem * *
    21. rem * masm lztimer; *
    22. rem * if errorlevel 1 goto errorend *
    23. rem * *
    24. rem * from this file. *
    25. rem * *
    26. rem * By Michael Abrash *
    27. rem ***************************************************************
    28. rem
    29. rem Make sure a file to test was specified.
    30. rem
    31. if not x%1==x goto ckexist
    32. echo ***************************************************************
    33. echo * Please specify a file to test. *
    34. echo ***************************************************************
    35. goto end
    36. rem
    37. rem Make sure the file exists.
    38. rem
    39. :ckexist
    40. if exist %1 goto docopy
    41. echo ***************************************************************
    42. echo * The specified file, "%1," doesn't exist. *
    43. echo ***************************************************************
    44. goto end
    45. rem
    46. rem copy the file to measure to TESTCODE.
    47. :docopy
    48. copy %1 testcode
    49. masm lztest;
    50. if errorlevel 1 goto errorend
    51. masm lztimer;
    52. if errorlevel 1 goto errorend
    53. link lztest+lztimer;
    54. if errorlevel 1 goto errorend
    55. lztest
    56. goto end
    57. :errorend
    58. echo ***************************************************************
    59. echo * An error occurred while building the long-period Zen timer. *
    60. echo ***************************************************************
    61. :end


     

  • 相关阅读:
    【Docker】容器连接到mysql(容器互联 超详细)
    python常用操作汇总
    下单小程序怎么做呢?
    21天学习挑战赛(2)图解设备树的使用
    三十六、java版 SpringCloud分布式微服务云架构之Java 泛型
    代码随想录-017-19.删除链表的倒数第N个节点
    【大数据】HDFS的使用与集群角色(学习笔记)
    【第62篇】DepGraph:适用任何结构的剪枝
    【MySQL】索引和事务(B树、B+树图解原理)
    HelloWorld - 从Houdini导出HDA到UE5
  • 原文地址:https://blog.csdn.net/hb_zxl/article/details/126845130