I need a reliable and stable timing method for a 32 bit protected mode DOS client. Has anyone done this before?
Gunther
for timing things, like code, we use Michael's timing macros
http://masm32.com/board/index.php?topic=49.0 (http://masm32.com/board/index.php?topic=49.0)
but, if you are talking about event triggers, you may want to use the multimedia timers
http://msdn.microsoft.com/en-us/library/windows/desktop/dd743609%28v=vs.85%29.aspx (http://msdn.microsoft.com/en-us/library/windows/desktop/dd743609%28v=vs.85%29.aspx)
they offer a little better resolution than the standard SetTimer
if you want time markers, RDTSC offers good resolution/speed (about 100 cylces)
GetTickCount is faster (about 10 cycles), but offers only 10 to 16 mS resolution
The nearest source codes that I have are the two FreeBASIC-DOS sources in the attachment. Except for the interface function and a few global variables, the essential parts are done in inline (GAS) assembly. And in case it's not obvious, the commenting is aimed at the "lowest common denominator" :biggrin:
Also, I doubt that it's likely to matter but in dostimer_posted I noticed that I forgot to undo the unmasking of IRQ8 in my destructor.
This is an older source that maximizes the short-term resolution at the expense of long-term accuracy.
'====================================================================
'' This function returns the elapsed seconds since the system was
'' started, or zero if the processor does not support the CPUID
'' or RDTSC instructions. The elapsed seconds are determined by
'' dividing the current Time Stamp Counter (TSC) value by the CPU
'' clock frequency, as determined in the first call by counting
'' the processor clock cycles over a (65536/1193182) second
'' interval timed with system timer 2.
'====================================================================
function HrTimer() as double
static as double clkhz
dim as ulongint tsc
dim as integer i
if clkhz = 0 then
'------------------------------------------------------------
'' CPUID supported if can set/clear ID flag (EFLAGS bit 21).
'------------------------------------------------------------
asm
pushfd
pop edx
pushfd
pop eax
xor eax, &h200000 '' flip ID flag
push eax
popfd
pushfd
pop eax
xor eax, edx
mov [i], eax
end asm
if i = 0 then return 0
'---------------------------------------------------------------
'' TSC supported if CPUID func 1 returns with bit 4 of EDX set.
'---------------------------------------------------------------
asm
mov eax, 1
cpuid
and edx, &h10
mov [i], edx
end asm
if i = 0 then return 0
'-----------------------------------------------------------
'' Set the gate for timer 2 (bit 0 at I/O port 61h) to OFF.
'-----------------------------------------------------------
out &h61, inp(&h61) and not 1
'----------------------------------------------------
'' Program timer 2 for LSB then MSB, mode 0, binary.
'' bit 7-6: 10 = timer 2
'' bit 5-4: 11 = R/W LSB then MSB
'' bit 3-1: 000 = single timeout
'' bit 0: 0 = binary
'----------------------------------------------------
out &h43, &hb0
'---------------------------------------------------------
'' Load the starting value, LSB then MSB. This value will
'' cause the timer to time out after 65536 cycles of its
'' 1193182 Hz clock.
'---------------------------------------------------------
out &h42, 0
out &h42, 0
'-------------------------------------------
'' Serialize and get the current TSC value.
'-------------------------------------------
asm
xor eax, eax
cpuid
rdtsc
mov [tsc], eax
mov [tsc+4], edx
end asm
'----------------------------------------------------------
'' Set the gate for timer 2 (bit 0 at I/O port 61h) to ON.
'----------------------------------------------------------
out &h61, inp(&h61) or 1
'------------------------------------------------------------
'' Wait until the output bit (bit 5 at I/O port 61h) is set.
'------------------------------------------------------------
wait &h61, &h20
'--------------------------------------------------
'' Serialize and calculate the elapsed TSC counts.
'--------------------------------------------------
asm
xor eax, eax
cpuid
rdtsc
sub eax, [tsc]
sbb edx, [tsc+4]
mov [tsc], eax
mov [tsc+4], edx
end asm
'-------------------------------------------------------------
'' Set the gate for timer 2 (bit 0 at I/O port 61h) to OFF.
'-------------------------------------------------------------
out &h61, inp(&h61) and not 1
'-------------------------------------------
'' Calc and save the processor clock speed.
'-------------------------------------------
clkhz = tsc / (65536 / 1193182)
end if
'-------------------------------------------
'' Serialize and get the current TSC value.
'-------------------------------------------
asm
xor eax, eax
cpuid
rdtsc
mov [tsc], eax
mov [tsc+4], edx
end asm
'---------------------------------------
'' Calc and return the elapsed seconds.
'---------------------------------------
return tsc / clkhz
end function
'====================================================================
And, although I cannot find the source ATM, I did multiple tests of a timer that read the running count for system timer 0 and combined it with the BIOS timer tick value. In theory this should effectively emulate a counter with a 1/1193182 = 838 ns period, but in my tests, running in PM, the effective resolution was in the millisecond range. I recall the I/O port accesses taking much longer than I expected.
Michael,
thank you for posting your source. :t The good news is: it'll work under PM; that's fine. I'll try to adopt your procedure for PowerBASIC. Would that be okay? I'll give credit, that's clear.
Gunther
Quote from: Gunther on August 15, 2013, 05:45:02 AM
I'll try to adopt your procedure for PowerBASIC. Would that be okay? I'll give credit, that's clear.
You're welcome to do anything you wish with it, no credit necessary. Would that be PowerBASIC for DOS? How are you going to set up 32-bit PM?
Quote from: dedndave on August 15, 2013, 03:19:37 AM
but, if you are talking about event triggers, you may want to use the multimedia timers
http://msdn.microsoft.com/en-us/library/windows/desktop/dd743609%28v=vs.85%29.aspx (http://msdn.microsoft.com/en-us/library/windows/desktop/dd743609%28v=vs.85%29.aspx)
they offer a little better resolution than the standard SetTimer
Just for completeness, there is QPC (as in MB's NanoTimer() (http://www.webalice.it/jj2006/MasmBasicQuickReference.htm#Mb1171)) with an effective resolution of about 0.3 microseconds.
i didn't mentioned QueryPerformanceCounter, because it's so slow - lol
i see he was doing something else, anyways
sounds like RDTSC would be a good choice for him - not sure
Quote from: dedndave on August 15, 2013, 11:18:16 AM
i didn't mentioned QueryPerformanceCounter, because it's so slow - lol
I am quite happy with QPC, actually, and it's the official M$ High-Resolution Timer (http://msdn.microsoft.com/en-us/library/windows/desktop/ms644900%28v=vs.85%29.aspx#high_resolution):
QuoteIf a high-resolution performance counter exists on the system, you can use the QueryPerformanceFrequency function to express the frequency, in counts per second. The value of the count is processor dependent. On some processors, for example, the count might be the cycle rate of the processor clock.
The QueryPerformanceCounter function retrieves the current value of the high-resolution performance counter. By calling this function at the beginning and end of a section of code, an application essentially uses the counter as a high-resolution timer. For example, suppose that QueryPerformanceFrequency indicates that the frequency of the high-resolution performance counter is 50,000 counts per second. If the application calls QueryPerformanceCounter immediately before and immediately after the section of code to be timed, the counter values might be 1500 counts and 3500 counts, respectively. These values would indicate that .04 seconds (2000 counts) elapsed while the code executed.
QPC gives very consistent timings. The only thing it doesn't do well is counting cycles.
i measured QPC, GetTickCount, and RDTSC
QPC 1000 cycles
RDTSC 100 cycles
GetTickCount 10 cycles
:P
Quote from: dedndave on August 15, 2013, 12:24:02 PM
i measured QPC, GetTickCount, and RDTSC
QPC 1000 cycles
RDTSC 100 cycles
GetTickCount 10 cycles
:P
I know, my GetTickCount does it in 4 cycles, but it's kind of a fake because somewhere in the background the memory location where GetTickCount gets his value gets updated. Unless a hardware timer writes directly to memory bypassing the CPU ::)
For practical purposes, the 1000 cycles of QPC are often irrelevant, because your testing loop runs a Million times anyways.
Hi Michael,
Quote from: MichaelW on August 15, 2013, 06:28:20 AM
You're welcome to do anything you wish with it, no credit necessary. Would that be PowerBASIC for DOS? How are you going to set up 32-bit PM?
thank you. Yes it's PowerBASIC for DOS. 32 bit protected mode is a bit tricky, but this plan of action will work:
- Start the program in Real Mode.
- Check VESA support.
- Check DPMI support. If so, switch into Protected Mode.
- Patch the called procedures during run time (exchange segments with valid selectors).
- Switch back to Real Mode, so PB can do the clean up and finish.
Gunther