Author Topic: Protected Mode timings  (Read 6289 times)

Gunther

  • Member
  • *****
  • Posts: 3517
  • Forgive your enemies, but never forget their names
Protected Mode timings
« on: August 15, 2013, 03:07:59 AM »
I need a reliable and stable timing method for a 32 bit protected mode DOS client. Has anyone done this before?

Gunther
Get your facts first, and then you can distort them.

dedndave

  • Member
  • *****
  • Posts: 8749
  • Still using Abacus 2.0
    • DednDave
Re: Protected Mode timings
« Reply #1 on: August 15, 2013, 03:19:37 AM »
for timing things, like code, we use Michael's timing macros
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
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

MichaelW

  • Global Moderator
  • Member
  • *****
  • Posts: 1209
Re: Protected Mode timings
« Reply #2 on: August 15, 2013, 03:54:19 AM »
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.
Code: [Select]
'====================================================================
'' 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.
Well Microsoft, here’s another nice mess you’ve gotten us into.

Gunther

  • Member
  • *****
  • Posts: 3517
  • Forgive your enemies, but never forget their names
Re: Protected Mode timings
« Reply #3 on: August 15, 2013, 05:45:02 AM »
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
Get your facts first, and then you can distort them.

MichaelW

  • Global Moderator
  • Member
  • *****
  • Posts: 1209
Re: Protected Mode timings
« Reply #4 on: August 15, 2013, 06:28:20 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?
Well Microsoft, here’s another nice mess you’ve gotten us into.

jj2007

  • Member
  • *****
  • Posts: 7738
  • Assembler is fun ;-)
    • MasmBasic
Re: Protected Mode timings
« Reply #5 on: August 15, 2013, 09:12:35 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
they offer a little better resolution than the standard SetTimer

Just for completeness, there is QPC (as in MB's NanoTimer()) with an effective resolution of about 0.3 microseconds.

dedndave

  • Member
  • *****
  • Posts: 8749
  • Still using Abacus 2.0
    • DednDave
Re: Protected Mode timings
« Reply #6 on: August 15, 2013, 11:18:16 AM »
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

jj2007

  • Member
  • *****
  • Posts: 7738
  • Assembler is fun ;-)
    • MasmBasic
Re: Protected Mode timings
« Reply #7 on: August 15, 2013, 11:39:07 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:
Quote
If 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.

dedndave

  • Member
  • *****
  • Posts: 8749
  • Still using Abacus 2.0
    • DednDave
Re: Protected Mode timings
« Reply #8 on: August 15, 2013, 12:24:02 PM »
i measured QPC, GetTickCount, and RDTSC
QPC 1000 cycles
RDTSC 100 cycles
GetTickCount 10 cycles
 :P

jj2007

  • Member
  • *****
  • Posts: 7738
  • Assembler is fun ;-)
    • MasmBasic
Re: Protected Mode timings
« Reply #9 on: August 15, 2013, 12:27:24 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.

Gunther

  • Member
  • *****
  • Posts: 3517
  • Forgive your enemies, but never forget their names
Re: Protected Mode timings
« Reply #10 on: August 16, 2013, 02:25:36 AM »
Hi Michael,

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
Get your facts first, and then you can distort them.