Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

More on Sleep - Yawn.

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • More on Sleep - Yawn.

    I had a need for a small delay and tried Sleep 1 but the delay was far too long. I then remembered that Sleep wasn't too good with small values but had forgotten why. Isn't old age wonderful.

    Anyway, I had a quick look at what PB did with Sleep m&. m& is pushed onto the stack and then the Sleep function in Kernel32 is invoked. My SDK docs didn't say much so I went to MSDN. Of course, it is linked to the system clock - now I remember. With a frequency of 64Hz we have a resolution of 15.625ms.

    I then saw this:
    "To increase the accuracy of the sleep interval, call the timeGetDevCaps function to determine the supported minimum timer resolution and the timeBeginPeriod function to set the timer resolution to its minimum. Use caution when calling timeBeginPeriod, as frequent calls can significantly affect the system clock, system power usage, and the scheduler. If you call timeBeginPeriod, call it one time early in the application and be sure to call the timeEndPeriod function at the very end of the application."

    I'm sure that I hadn't seen that before.

    With
    Code:
    Local Time As TIMECAPS
    TimeGetDevCaps( Time, SizeOf(Time) )
    I get Time.wPeriodMin = 1ms

    With the standard Sleep the following
    Code:
    #Compile  Exe
    #Register None
    #Dim      All
    #TOOLS    OFF
     
    %NOMMIDS = 1
    %NOGDI = 1
     
    #Include "WIN32API.INC"
     
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Global qFreq, qOverhead, qStart, qStop As Quad
    Global f As String
     
    Macro InitializeTimer
      f = "#####.###"
      QueryPerformanceFrequency qFreq
      QueryPerformanceCounter qStart ' Intel suggestion. First use may be suspect
      QueryPerformanceCounter qStart ' So, wack it twice <smile>
      QueryPerformanceCounter qStop
      qOverhead = qStop - qStart     ' Relatively small
    End Macro
     
    Macro StartTimer = QueryPerformanceCounter qStart
    Macro StopTimer = QueryPerformanceCounter qStop
     
    Macro sTimeTaken = Using$(f,(qStop - qStart - qOverhead)*1000/qFreq) + "ms"
    Macro nTimeTaken = (qStop - qStart - qOverhead)*1000/qFreq
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    Function PBMain() As Long
    Local Time As TIMECAPS
    Local n As Long
     
      InitializeTimer
     
      'TimeGetDevCaps( Time, SizeOf(Time) )
      'TimeBeginPeriod(Time.wPeriodMin)
     
      For n = 50 To 1 Step -1
        StartTimer
          Sleep n
        StopTimer
        Print n;" ";sTimeTaken
      Next
     
      'TimeEndPeriod(Time.wPeriodMin)
     
      Waitkey$
     
    End Function
    gives
    Code:
     50     53.006ms
     49     62.330ms
     48     62.390ms
     47     62.392ms
     46     46.781ms
     45     46.709ms
     44     46.751ms
     43     46.751ms
     42     46.609ms
     41     46.708ms
     40     46.742ms
     39     46.748ms
     38     46.763ms
     37     46.755ms
     36     46.772ms
     35     46.759ms
     34     46.761ms
     33     46.752ms
     32     46.767ms
     31     31.131ms
     30     31.081ms
     29     31.100ms
     28     31.147ms
     27     31.118ms
     26     31.146ms
     25     31.120ms
     24     31.138ms
     23     31.140ms
     22     31.155ms
     21     31.130ms
     20     31.148ms
     19     31.143ms
     18     31.149ms
     17     31.126ms
     16     31.142ms
     15     15.439ms
     14     15.518ms
     13     15.503ms
     12     15.531ms
     11     15.507ms
     10     15.568ms
     9     15.407ms
     8     15.568ms
     7     15.524ms
     6     15.550ms
     5     15.532ms
     4     15.538ms
     3     15.547ms
     2     15.550ms
     1     15.536ms
    On removing the comment tags I got
    Code:
     50     49.233ms
     49     49.613ms
     48     48.756ms
     47     47.773ms
     46     46.786ms
     45     45.828ms
     44     44.847ms
     43     43.876ms
     42     42.902ms
     41     40.943ms
     40     39.967ms
     39     38.973ms
     38     38.015ms
     37     37.043ms
     36     36.062ms
     35     35.087ms
     34     34.115ms
     33     33.099ms
     32     32.158ms
     31     31.185ms
     30     30.210ms
     29     29.231ms
     28     28.257ms
     27     27.270ms
     26     26.298ms
     25     25.298ms
     24     24.345ms
     23     23.367ms
     22     22.397ms
     21     21.413ms
     20     20.441ms
     19     19.451ms
     18     18.486ms
     17     17.508ms
     16     16.536ms
     15     15.561ms
     14     14.587ms
     13     13.626ms
     12     12.603ms
     11     11.652ms
     10     10.683ms
     9      9.729ms
     8      8.695ms
     7      7.749ms
     6      6.773ms
     5      5.798ms
     4      4.818ms
     3      3.846ms
     2      2.870ms
     1      1.892ms
    That is better.

    Depending upon the application the above may do without further ado.

    On the other hand it may not.

    For very small values QueryPerformanceCounter is unrivalled.

    Enter HiResPause(n)
    Code:
    Macro HiResPause(n)
    MacroTemp ts, te
    Dim ts As Quad, te As Quad
    QueryPerformanceCounter ts
    Do
      QueryPerformanceCounter te
    Loop Until (te - ts )*1000000 >= n * qFreq
    End Macro
    With
    Code:
    #Compile  Exe
    #Register None
    #Dim      All
    #TOOLS    OFF
     
    %NOMMIDS = 1
    %NOGDI = 1
     
    #Include "WIN32API.INC"
     
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Global qFreq, qOverhead, qStart, qStop As Quad
    Global f As String
     
    Macro InitializeTimer
      f = "#####.###"
      QueryPerformanceFrequency qFreq
      QueryPerformanceCounter qStart ' Intel suggestion. First use may be suspect
      QueryPerformanceCounter qStart ' So, wack it twice <smile>
      QueryPerformanceCounter qStop
      qOverhead = qStop - qStart     ' Relatively small
    End Macro
     
    Macro StartTimer = QueryPerformanceCounter qStart
    Macro StopTimer = QueryPerformanceCounter qStop
     
    Macro sTimeTaken = Using$(f,(qStop - qStart - qOverhead)*1000/qFreq) + "ms"
    Macro nTimeTaken = (qStop - qStart - qOverhead)*1000/qFreq
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Macro HiResPause(n)
    MacroTemp ts, te
    Dim ts As Quad, te As Quad
    QueryPerformanceCounter ts
    Do
      QueryPerformanceCounter te
    Loop Until (te - ts )*1000000 >= n * qFreq
    End Macro
    '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
     
    Function PBMain() As Long
    Local n As Long
     
      InitializeTimer
     
      For n = 20 To 1 Step -1
        StartTimer
          HiResPause(n*1000)
        StopTimer
        Print n;" ";sTimeTaken
      Next
     
      Waitkey$
     
    End Function
    I get
    Code:
     20     20.000ms
     19     19.000ms
     18     18.000ms
     17     17.000ms
     16     16.000ms
     15     15.000ms
     14     14.000ms
     13     13.000ms
     12     12.000ms
     11     11.000ms
     10     10.000ms
     9      9.000ms
     8      8.000ms
     7      7.000ms
     6      6.000ms
     5      5.000ms
     4      4.000ms
     3      3.000ms
     2      2.000ms
     1      1.000ms
    In practice we would not use use InitializeTimer but simply 'QueryPerformanceFrequency qFreq' and then use HiResPause.

    There is a major difference between Sleep and HiResPause, which is why I did not use HiResSleep, and that is whilst Sleep "causes a thread to relinquish the remainder of its time slice and become unrunnable for an interval based on the value of" m&, HiResPause, on the contrary, opens the throttle wide open pushing the CPU to the limit. However, this will not be problematic if we restrict HiRespause to small values of m&.

    We could implement the following.
    Code:
    If n >= 20 Then
      Sleep n
    Else
      HiResPause(n*1000)
    End If
    so that HiResPause is only invoked with m& < 20ms.

    Of course, HiResPause may be used for intervals less than 1ms.

    Here are some examples.

    (250) 0.250ms
    (100) 0.100ms
    (50) 0.051ms
    (10) 0.011ms

    There is a small error in the last two but we are looking at very small intervals here. (10) is 10 millionth of a second.

  • #2
    David:

    It's a very interesting subject, and very useful in my area of concern.

    Thanks you!

    Comment


    • #3
      I'm glad to here that the above was useful for you Manuel but we should not reply with comments only in the Source Code forums.

      -------------------------------------

      On examining the behaviour of both the Standard Sleep, using the System Timer, and the Enhanced Sleep, using a Multi-media Timer via the TimeBeginPeriod/TimeEndPeriod pairing, the latter very much more often than not gives an actual sleep of the chosen value plus a value >= 0ms and < 1ms. Occasionally, the actual sleep is slightly less than the chosen value but I will ignore those for the moment.

      It follows then that if we request 'Sleep n - 1' the actual sleep will be between 'n - 1' and 'n'.

      On completion of 'Sleep n - 1' we can suspend execution further by querying the Performance Counter until a target value is achieved. The target value will, of course, correspond to an initial condition plus n-milliseconds. The initial condition will need to be got before executing 'Sleep n - 1' and then advanced to the target value, which I've called qTimeOut, via a simple equation involving the Performance Counter overhead, its frequency and, of course, 'n'.

      This approach should give us the same accuracy as HiResPause, which has been rewritten - see later, but with very little CPU usage; unlike HiResPause. There is no CPU usage during the first phase, that is the Sleep phase, but there is some during the second phase, that is the Performance Counter phase.

      Some tests were carried out using the 'Free Extended Task Manager by Extensoft' and CPU usage during the second phase occasionally peaked at 3%. I assume that these events occurred when the Performance Counter was being used at the high end of the >= 0ms and < 1ms range.

      This approach then is unlikely to compromise anyone's system.

      I mentioned above that occasionally, the actual sleep is slightly less than the chosen value. In these instances we could use 'Sleep n' as opposed to 'Sleep n - 1' but we cannot know, a priori, when they will occur so we must stay will 'Sleep n - 1'. In these instances the second phase will last for slightly more than 1ms.

      Here is the macro, which I cannot call HiResSleep because it is a hybrid of the Enhanced Sleep and the Performance Counter.

      Code:
      Macro HiResHybridSleep(n)
      MacroTemp qTimeOut, qNow
      Dim qTimeOut As Quad, qNow As Quad
      QueryPerformanceCounter qNow
      qTimeOut = qNow - qOverhead + n*qFreq/1000
      Sleep n-1
      Do
        QueryPerformanceCounter qNow
      Loop Until qNow >= qTimeOut
      End Macro
      Since both qOverhead and qFreq are required then I recommend including the timer macros even though you may not need them as qOverhead and qFreq are determined in the InitializeTimer macro.

      Here is the result of one test; figures in ms.

      Code:
      200  200.000251
      180  180.000270
      160  160.000187
      140  140.000217
      120  120.000217
      100  100.000161
       80   80.000184
       60   60.000277
       40   40.000221
       20   20.000232
       19   19.000105
       18   18.000236
       17   17.000146
       16   16.000330
       15   15.000221
       14   14.000247
       13   13.000247
       12   12.000165
       11   11.000262
       10   10.000127
        9    9.000105
        8    8.000255
        7    7.000259
        6    6.000180
        5    5.000255
        4    4.000251
        3    3.000319
        2    2.000124
      The worst case here is where we overshoot 16ms by 330ns.

      The accuracy, of course, is related the the Performance Counter frequency and a friend's PC with an old P4 managed only 3 to 4 µs accuracy. I say 'only' but 3 to 4 µs is still blindingly accurate. [1]

      Notice that the table only goes down to 2ms. Here we are using 'Sleep 1' as the base and that is our limit.

      If delays less than 2ms are required then use HiResPause(n) which has been rewritten as follows.

      Code:
      Macro HiResPause(n)
      MacroTemp qTimeOut, qNow
      Dim qTimeOut As Quad, qNow As Quad
      QueryPerformanceCounter qNow
      qTimeOut= qNow - qOverhead + n*qFreq/1000000
      Do
        QueryPerformanceCounter qNow
      Loop Until qNow >= qTimeout
      End Macro
      Don't forget that HiResPause expects µs input so 1.35ms will require HiResPause(1350).

      I don't expect HiResHybridSleep to cause any problems but if it does then please, for the benefit of everyone, report such here.

      [1] I originally wrote ms instead of µs. I got my minis and micros mixed up. Should I rephrase that? <grin>
      Last edited by David Roberts; 2 Jun 2009, 05:56 AM.

      Comment


      • #4
        I have been doing some work with my Timer Macros and finally advocated using a TIX version but only if the Performance Counter uses the Time Stamp Counter as does TIX.

        The overhead with TIX is about 17% of the overhead with QueryPerformanceCounter.

        The TIX statement in the manual looks at 'TIX QuadVar' and 'TIX END QuadVar'.

        We will get the same result from the following:

        TIX QuadVar
        ...
        ...
        TIX QuadVar1

        QuadVar = QuadVar1 - QuadVar

        Of course, 'TIX END' simplifies matters but the point I am making is that TIX may be used for continual reading in the same way as QueryPerformanceCounter.

        Becuase TIX has a much smaller overhead than the QPC it occurred to me that we may get a better resolution with the above macros.

        With HiResHybridSleep now written as:-

        Code:
        Macro HiResHybridSleep(n)
        MacroTemp qTimeOut, qNow
        Dim qTimeOut As Quad, qNow As Quad
        Tix qNow
        qTimeOut = qNow - qOverhead + n*qFreq/1000
        Sleep n-1
        Do
          Tix qNow
        Loop Until qNow >= qTimeOut
        End Macro
        I now get:_

        Code:
        200  200.000046
        180  180.000031
        160  160.000020
        140  140.000016
        120  120.000016
         80   80.000043
         60   60.000024
         40   40.000020
         20   20.000043
         19   19.000024
         18   18.000031
         17   17.000020
         16   16.000021
         15   15.000025
         14   14.000025
         13   13.000028
         12   12.000028
         11   11.000021
         10   10.000021
          9    9.000021
          8    8.000010
          7    7.000021
          6    6.000021
          5    5.000010
          4    4.000013
          3    3.000013
          2    2.000006
        Using the QPC the worst case had an overshoot of 330ns. The worst case above is 46ns.

        That is 46 one thousand-millionth of a second. Not bad for a PC.

        All we have done is replace QueryPerformanceCounter with TIX and got a factor of 10 more accuracy and this is simply got by avoiding the API overhead and nothing else.

        We can, of course, do the same with HiResPause:-

        Code:
        Macro HiResPause(n)
        MacroTemp qTimeOut, qNow
        Dim qTimeOut As Quad, qNow As Quad
        Tix qNow
        qTimeOut= qNow - qOverhead + n*qFreq/1000000
        Do
          Tix qNow
        Loop Until qNow >= qTimeout
        End Macro
        I must stress again that these new versions will only work if the Performance Counter uses the Time Stamp Counter as does TIX.

        ADDED: You will, of course, need the TIX version of the Timer macros here.
        Last edited by David Roberts; 9 Apr 2010, 09:44 PM.

        Comment

        Working...
        X