-
Jul 10th, 2014, 04:07 PM
#1
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.
-
Jul 10th, 2014, 04:16 PM
#2
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
-
Jul 10th, 2014, 04:22 PM
#3
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.
-
Jul 10th, 2014, 04:32 PM
#4
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
-
Jul 10th, 2014, 04:48 PM
#5
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.
-
Jul 10th, 2014, 05:48 PM
#6
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.
-
Jul 10th, 2014, 05:51 PM
#7
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
-
Jul 11th, 2014, 09:14 AM
#8
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.
-
Jul 11th, 2014, 09:48 AM
#9
Re: High Precision Timer
Originally Posted by Shaggy Hiker
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).
-
Jul 11th, 2014, 12:27 PM
#10
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.
-
Jul 11th, 2014, 02:31 PM
#11
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.
-
Jul 11th, 2014, 02:47 PM
#12
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.
-
Jul 11th, 2014, 06:18 PM
#13
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|