dcsimg
Results 1 to 13 of 13

Thread: High Precision Timer

  1. #1

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    9,544

    High Precision Timer

    Below is a high precision timer that uses the QueryPerformance API's. I tried to structure it just like a normal timer would be so that it makes it easier to use.

    My might you want to use this timer over normal timer? Well, in my case I needed a game loop that will be consistently executed. With the normal timer, any intervals around 15 - 20 milliseconds are no longer accurate and for a game loop you want an interval of 16.6 milliseconds(60 FPS).

    Here is the code:
    Code:
    Option Strict On
    Option Explicit On
    <System.ComponentModel.DefaultEvent("Tick")> _
    Public Class PrecisionTimer
        Inherits System.ComponentModel.Component
    
        Private frequency As Long
        Private waitThread As Threading.Thread
    
    #Region "Api"
    
        Private Declare Function QueryPerformanceCounter Lib "kernel32" (ByRef lpPerformanceCount As Long) As Integer
        Private Declare Function QueryPerformanceFrequency Lib "kernel32" (ByRef lpFrequency As Long) As Integer
    
    #End Region
    
    #Region "Events"
    
        Public Event Tick(ByVal sender As Object, ByVal e As EventArgs)
    
    #End Region
    
    #Region "Methods"
    
        Private Sub CheckCompatibility()
            Dim test As Long
            If Not CBool(QueryPerformanceCounter(test)) Then
                Throw New Exception("High-resolution counter is not supported for this computer.")
            End If
        End Sub
    
        Public Sub Start()
            Me.Enabled = True
            waitThread = New Threading.Thread(AddressOf Wait)
            waitThread.IsBackground = True
            waitThread.Start()
        End Sub
    
        Public Sub [Stop]()
            Me.Enabled = False
        End Sub
    
        Private Sub Wait()
            Dim counter1, counter2 As Long
            QueryPerformanceCounter(counter1)
    
            If Me.LowerCpuUsage Then
    
                Do
                    QueryPerformanceCounter(counter2)
                    Threading.Thread.Sleep(2)
                Loop Until (counter2 - counter1) / (frequency / 1000) >= Me.Interval
    
            Else
    
                Do
                    QueryPerformanceCounter(counter2)
                Loop Until (counter2 - counter1) / (frequency / 1000) >= Me.Interval
    
            End If
    
            Console.WriteLine((counter2 - counter1) / (frequency / 1000))
    
            RaiseEvent Tick(Me, EventArgs.Empty)
    
            If Me.AutoReset Then
                Me.Enabled = False
            ElseIf Me.Enabled Then
                waitThread = New Threading.Thread(AddressOf Wait)
                waitThread.Start()
            End If
    
        End Sub
    
    #End Region
    
    #Region "New Constructor"
    
        Sub New()
            Call CheckCompatibility()
            QueryPerformanceFrequency(frequency)
            Me.Interval = 100
        End Sub
    
        Sub New(ByVal interval As Double)
            Call CheckCompatibility()
            QueryPerformanceFrequency(frequency)
            Me.Interval = interval
        End Sub
    
    #End Region
    
    #Region "Properties"
    
        Public Property AutoReset As Boolean
        Private pEnabled As Boolean
        Public Property Enabled() As Boolean
            Get
                Return pEnabled
            End Get
            Set(ByVal value As Boolean)
                If pEnabled <> value Then
                    pEnabled = value
                    If pEnabled Then RaiseEvent Tick(Me, EventArgs.Empty)
                End If
            End Set
        End Property
        Public Property Interval As Double
        Public Property LowerCpuUsage As Boolean
    
    #End Region
    
    End Class
    I'd like to thank Jacob Roman for introducing me to the QueryPerformance API's.
    Last edited by dday9; Jul 11th, 2014 at 05:18 PM.

  2. #2
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    34,245

    Re: High Precision Timer

    Won't this act as a busy wait in that it would fully occupy one core of the CPU? If so, you might consider something like a Thread.Sleep(N), where N >=2. That will mean that the CheckPerformanceCounter won't be called again immediately, but it would mean that the accuracy would degrade because you could be up to N milliseconds late in a tick. The larger the value of N that is used, the worse the accuracy, but the lighter the load on the CPU, I would expect.
    My usual boring signature: Nothing

  3. #3

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    9,544

    Re: High Precision Timer

    It shouldn't cause much of a memory hog. I just checked it in my task manager with the interval at 16.6 milliseconds and the memory usage stayed at around 4,000 k.

  4. #4
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    34,245

    Re: High Precision Timer

    It's not memory that I'd be worried about, it's CPU usage. It seems like that loop should peg a CPU core just like any other busy wait, which will lead to excessive power consumption, extra heat production by the CPU, and the heartbreak of psoriasis. The point of the Thread.Sleep is so that the thread will yield time periodically.
    My usual boring signature: Nothing

  5. #5

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    9,544

    Re: High Precision Timer

    That's true, something I didn't think of. I'm running on a two core(I think) system right now and whenever I ran it, my CPU usage was at 50%. I suppose that's the trade off though, for ultra-high precision you sacrifice CPU usage.

  6. #6
    Angel of Code Niya's Avatar
    Join Date
    Nov 2011
    Posts
    5,639

    Re: High Precision Timer

    Hmmm...It spawns another thread so I'll have to sync the tick with the UI to use it as a game loop. I may have a couple ideas to deal with high core usage. When I get a chance I'll rig it up in DemonArena and see how it works.
    Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading | Simple file compression | Demon Arena


    C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter

    There's just no reason to use garbage like InputBox. -jmcilhinney

  7. #7
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    34,245

    Re: High Precision Timer

    Yes, and power consumption. This would be a killer on a battery system. I put a thread over in the General Programming forum where I looked at the power consumption of my computer with two apps, one had a busy wait and the other didn't. Even with a coarse power monitoring tool, the jump in current draw was noticeable.

    However, the basic idea isn't a bad one, I would just suggest an improvement. What you have now is a system that will do the timing with insane accuracy and precision. You had a problem with the timer when it got down to a few dozen milliseconds, so you went and beat it into submission with a timer of super high precision....at a cost. If you were to add a Thread.Sleep(N) into that method, then you could tune the performance of your timer by setting N to different values. Set it to 0 and you get all the precision. Set it to 10 and you get a timer more precise and accurate than the built in one, but you don't pay the performance penalty. In fact, somebody (I forget who), pointed out that using N=1 or 2 cuts the CPU usage by a HUGE amount, which is correct. That counter runs in microseconds, so waiting for a millisecond means that the CPU is resting more than it is running, yet your precision and accuracy is only off by a millisecond.

    You'd have to look up Thread.Sleep(0), though, because that may not be the same as "don't sleep at all".
    My usual boring signature: Nothing

  8. #8

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    9,544

    Re: High Precision Timer

    I've updated the code by adding a LowerCpuUsage property. And here is a breakdown of the property being True or False(tested on my Dual CPU processor with the interval at 100):
    LowerCpuUsage Advantages Disadvantages
    False The timer consistently raised the Tick event at 100ms The CPU usage was %50
    True The CPU usage was %5 The timer would often raise the Tick event 2-10ms after 100ms

    The lower the interval, the wilder the accuracy became with the LowerCpuUsage set to True.

  9. #9
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    5,551

    Re: High Precision Timer

    Quote Originally Posted by Shaggy Hiker View Post
    Won't this act as a busy wait in that it would fully occupy one core of the CPU? If so, you might consider something like a Thread.Sleep(N), where N >=2. That will mean that the CheckPerformanceCounter won't be called again immediately, but it would mean that the accuracy would degrade because you could be up to N milliseconds late in a tick. The larger the value of N that is used, the worse the accuracy, but the lighter the load on the CPU, I would expect.
    I've played around with timing things like this quite a bit over the years, as my job has mostly been Simulation type stuff, and sometimes we work with real equipment and the timing has to be fairly regulated sometimes.
    I like to do a short sleep to reduce the power consumption, and use code to maintain a running balance so I know it will run at the desired frequency, but with a "jitter" of +/- that 1 to 2 ms from ideal.

    The issue you usually have to test for though is that even on today's machines, (and maybe more so, for some reason), you have Windows systems that when you ask for a Thread.Sleep of 2, will not sleep for 2 ms but have the minimum sleep period of 15.625 (i.e. 64 hz). So, on those machines, if you use Thread.Sleep the fastest your application will cycle is 64hz.
    Windows is completely inconsistent, across the various hardware manufacturers, which is one of the big frustrations with a good timer across all installations (other than sacrificing processing and power to the busy loop).
    There are more expensive options, such as installing a real-time service that runs at level 0 of the kernel so can have the priority and resource to provide a good scheduler.
    It is unfortunate that the graphic card vertical sync timing isn't exposed in a simple manner that could be used, as it was back in the pre-windows days of VGA (simple means not having to go through directX or other 3D-library to get to it).

  10. #10

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    9,544

    Re: High Precision Timer

    The issue you usually have to test for though is that even on today's machines, (and maybe more so, for some reason), you have Windows systems that when you ask for a Thread.Sleep of 2, will not sleep for 2 ms but have the minimum sleep period of 15.625 (i.e. 64 hz). So, on those machines, if you use Thread.Sleep the fastest your application will cycle is 64hz.
    I suppose the timeBeginPeriod function can be used to fix that issue. Whenever I do that, it help with preventing a lot of the jitter, but the accuracy still suffers at the lower intervals.

  11. #11
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    5,551

    Re: High Precision Timer

    Yes, I've know about timeBeginPeriod for a long time, but have always been hesitant to use it because of the global nature of it, and the unknown burden added to the Windows OS with the increased process/thread context switching overhead.
    But I did just test it on my work machine, which I don't have the Admin privileges to do most anything, like installing software, and Win7 doesn't prevent my changing the timeBeginPeriod interval.
    So, on my work provided HP EliteBook 8470p, the default Sleep(1) period is actually the 15.625 ms, not 1ms as demonstrated by the following code.
    Code:
    Imports System.Threading
    
    Public Class Form1
      Private Declare Function timeBeginPeriod Lib "winmm.dll" (ByVal uPeriod As Integer) As Integer
      Private Declare Function timeEndPeriod Lib "winmm.dll" (ByVal uPeriod As Integer) As Integer
    
      Dim wtime(10) As Long
      Dim ticks(10) As Long
    
      Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Dim sw As New Stopwatch
        Dim i As Integer
        '  timeBeginPeriod(1)
        sw.Start()
        For i = 0 To 10
          Thread.Sleep(1)
          wtime(i) = sw.ElapsedMilliseconds
          ticks(i) = sw.ElapsedTicks
        Next
        '  timeEndPeriod(1)
        For i = 1 To 10
          Debug.WriteLine(String.Format("{0,8}{1,8}", (wtime(i) - wtime(i - 1)).ToString, (ticks(i) - ticks(i - 1)).ToString))
        Next
        Debug.Print(String.Format("{0,8}{1,10}", ((wtime(10) - wtime(0)) / 10).ToString, ((ticks(10) - ticks(0)) / 10)).ToString)
      End Sub
    End Class
    Output: (Elapsed milliseconds and ticks per cycle as measured by the stopwatch object (which uses the QueryPerformanceCounter))
    Last value is the average per cycle over 10 cycles
    Code:
          15   38466
          16   39583
          16   39582
          15   39548
          16   39460
          15   39419
          16   39579
          16   39604
          15   39449
          16   39868
        15.6   39455.8
    If you uncomment the timeBeginPeriod and timeEndPeriod, the output changes to the following:
    Code:
          16   39502
           1    2382
           1    2529
           1    2533
           1    2603
           1    2462
           1    2666
           1    2534
           1    2542
           1    2521
         2.5    6227.4
    The first time captured is still long, since the adjustment to the timePeriod won't effect a timePeriod already in progress, but all the succeeding sleeps are indeed 1ms, give or take around 7% of a millisecond (over this short period and based on elapsed tickcount between cycles).
    The last average doesn't count because of the initial "default" cycle being there.
    Should add a Thread Sleep call before entering the loop to allow the existing timePeriod interval to be "used up", before doing the timing cycle.

    Anyway, something to consider. I would like to experiment further to see if the extra context switching overhead of the Operating System is detectable as a drop of throughput for several CPU intensive tasks running, or in general by observing increased CPU using rising just by changing those values, without any additional processes being added to the mix.
    There are almost always tradeoffs, so I would like to have a better idea of what those may be.

    p.s. I just did some preliminary test, where I change the timePeriod interval, then did a fairly long sleep (10 seconds). I was just monitoring the CPU Perfomance as indicated in the Performance tab of the Windows Task Manager (I know, not very accurate) for a period before changing the time period, then watching for that 10 second period to see if I could detect an obvious increase in the trend of CPU Usage during that 10 second period, compared to before and after. Doesn't seem like it is much of an impact at all, given the machine isn't heavily loaded with a lot of intense tasks. The CPU usage is only indicating 0 to 1% the majority of the time (more 0% than 1%) before the change, and perhaps is increasing a little for the duration (more 1% than 0%), but not a major, obvious drag on the system.
    Need to kick off a few cpu intensive tasks running in parallel that can count throughput average, and then change the clock period to see if there is an obvious drop in throughput. If not, I'll probably strongly consider changing the time period if I have a time cycle critical task that is running on a Windows box (for convenience), rather than on a Real-Time Operating System box, in the future.
    Last edited by passel; Jul 11th, 2014 at 03:04 PM.

  12. #12

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    9,544

    Re: High Precision Timer

    There are almost always tradeoffs, so I would like to have a better idea of what those may be.
    Me too. It's trying to find the happy balance between pinpoint accuracy and low CPU usage that seems to be elusive.

  13. #13
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    5,551

    Re: High Precision Timer

    Might want to do a search for "Timers, Timer Resolution, and Development of Efficient Code" at http://msdn.microsoft.com/en-US/libr...dware/dn550976 (assuming you have something that can read a Word .docx file), if you haven't already.

    p.s. I can't run the PowerCfg command-line utility mentioned in the paper (on my work machine) as I don't have sufficient privilege.

    p.p.s One "take-away" from that paper might be that it is more efficient to keep one CPU in a high power state (i.e. idle loop), rather than shorten the timer interval globally so that all cores are expending more energy transitioning in and out of the idle state than they would be if they just remained in the high power state. Don't know if that is true as the CPU usage indication doesn't seem to support that, but the real energy being expended to transition power states is technically not CPU usage, so CPU usage is not necessarily a good indication of the actual energy being expended when you reduce the timer interval.
    Once more tidbit of information in the tradeoffs decision tree.
    Last edited by passel; Jul 11th, 2014 at 06:44 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width