-
Oct 21st, 2016, 10:09 AM
#1
Thread Starter
Member
MicroTimer, Resource Friendly Accurate Timer
So I was working on a parallel game a while back and had the need for an accurate timer as .Net doesn't provide one out of the 3 usual timer classes. I originally started out using a typical loop and polled the stopwatch but that was way too resource intensive. I eventually found the multimedia timer and it was accurate to the millisecond but games that ran at 60fps for example has each frame every 16.67 milliseconds and the multimedia timer simply cant achieve that. I then combined the 2 to get what is now my MicroTimer. It is accurate as the loop/poll (or close as can be to it), but resource friendly like a typical timer (unless you are going to high rates, in which case it will eat cpu like the loop/poll).
Source: http://www.vbforums.com/showthread.p...=1#post5151717
If anyone has any suggestions or improvements, please share. Thanks.
Last edited by TizzyT; Mar 22nd, 2017 at 09:22 PM.
-
Oct 24th, 2016, 05:49 PM
#2
Re: MicroTimer, Resource Friendly Accurate Timer
What does this do to CPU usage? What concerns me is the spin wait in that first do loop. As noted in this thread:
http://www.vbforums.com/showthread.p...ath&highlight=
I found that spin waits have a significant cost in terms of power consumption. Running the spin wait in a background thread shouldn't solve that problem as far as I can see, so the solution might prove to be horrible on battery powered devices, as the increased power consumption will suck the battery down right fast.
One way that might reduce that would be to add a Thread.Sleep statement in the loop. Of course, this would reduce the resolution of the loop, but it will greatly reduce power consumption and CPU load. If you only need a resolution better than 16 Hz, you could probably add a Thread.Sleep(5) and see the CPU load drop away almost completely, while still being pretty accurate.
It would be hard to find such a system, but spin waits should have an even bigger impact on single core processors, since the thread will be fully using all of its time slices, which will mean a LOT of context switches as the main and background threads switch in and out. Of course, single core systems are all legacy, by now, so it may not matter. The CPU load is still an issue, but the threads can run on different cores and avoid the context switching costs.
My usual boring signature: Nothing
-
Oct 24th, 2016, 06:37 PM
#3
Re: MicroTimer, Resource Friendly Accurate Timer
The spin wait is only when you have less than 1 ms left, so the majority of the wait is based on being awaken to the nearest 1 ms using the multimedia timer and a queued callback.
I haven't tested it, but the whole purpose of creating this timer was to reduce the CPU usage and still have the accuracy of a "regular" free running game loop.
In another thread TizzyT said that the CPU usage in his testing was 1% or less.
p.s. The main reason I haven't tested yet is because of the async await task lambda methodology. I only have vs 2010 on my work computer, without the additional update that added that capability to 2010, and no permissions to install an update.
I do have 2013 and 2015 on my home laptops, but just haven't gotten around to testing it there.
Last edited by passel; Oct 24th, 2016 at 06:51 PM.
-
Oct 24th, 2016, 10:35 PM
#4
Thread Starter
Member
Re: MicroTimer, Resource Friendly Accurate Timer
@passel is correct. The looping only happens after the time (in ms) has elapsed which calls that loop. The loop will then poll (only within that ms, which is how it can achieve <1ms precision) the stopwatch until the specified time has elapsed which will then trigger the Elapsed event. It then uses the multimedia timer to wait the specified ms again before calling the loop again etc. There is also lag compensation in case the Elapsed routine takes too long it will play catch up until NextTime is no longer less then the current time. I recognized that a typical loop eats crazy CPU and thus I came up with this. It would be disheartening if the goal for this timer was to reduce CPU but in the end it was for nothing lol, but rest assured it is resource friendly (at least from my testing) like the thread implies
Edit: Maybe I should change out the async/await for the old fashioned threading.thread and thread.start?
Last edited by TizzyT; Oct 24th, 2016 at 11:53 PM.
-
Oct 25th, 2016, 08:26 AM
#5
Re: MicroTimer, Resource Friendly Accurate Timer
I'm sure if I took the time to understand the thread interaction I could make it compatible with the base vb.net 2010 install, and I did start looking at it, but then figured I couldn't really afford the time right now, so its something that I might get around to someday. I currently use a similar technique to handle the regulation and lag compensation in a background process, but using sleep so accept the few milliseconds of jitter that results from doing it that way (and also don't expect to exceed 64hz tick rate), so it works well enough for my needs.
But if you wanted to make it backward compatible yourself, I wouldn't mind having a better option sooner, than later.
In other cases, I have a UDP message coming in from an external hardware synchronized source, so I can use that as a tick source so that I don't have to have a timer when that source is available, so don't have a pressing need to change at the moment.
-
Oct 25th, 2016, 09:55 AM
#6
Thread Starter
Member
Re: MicroTimer, Resource Friendly Accurate Timer
Sure @passel, After I get home from class today I'll change those to use the threading.thread class. It would be great news if my code was of some use to people
-
Oct 25th, 2016, 11:59 AM
#7
Re: MicroTimer, Resource Friendly Accurate Timer
Ok, that's a good way to go about it. A similar spin wait is used internally by the OS for a similar situation.
I was under the impression (based on one note, but ZERO testing) that the Asynch/Await had less overhead than the raw threads. One thing this means is that you may not want to use the raw threads, but more importantly: If you DO implement this with Threading.Thread, try to figure out the comparative performance. Offhand, that seems like such a difficult thing to do it barely seems worth it, but it would certainly be interesting to get some empirical comparison between the two.
My usual boring signature: Nothing
-
Mar 19th, 2017, 11:26 AM
#8
Thread Starter
Member
Re: MicroTimer, Resource Friendly Accurate Timer
Okay I know its been a while but I finally got around to working on my MicroTimer again and now it uses about 0.0% CPU (on my system). I also got rid of the Async/Awaits. This also allows you to specify levels of precision. I also found some weird threading/memory leak issue so I decided to make it a single instance scheme via making everything shared (might fix some other time, or someone else can and post back?, although don't see the need as why would one need more than one MicroTimer in a game). Without further adieu here is the SOURCE.
Last edited by TizzyT; Mar 19th, 2017 at 11:34 AM.
-
Mar 20th, 2017, 09:10 AM
#9
Re: MicroTimer, Resource Friendly Accurate Timer
you might want to attach the source
pastebin is about as reliable (read: brittle) as all the rest of the file sharing sites.
-
Mar 22nd, 2017, 12:14 PM
#10
Thread Starter
Member
Re: MicroTimer, Resource Friendly Accurate Timer
As suggested here is the source:
Code:
Imports System.Runtime.InteropServices
Imports System.Threading
Public Class MicroTimer
Public Shared Event Elapsed()
#Region "Misc"
Public Shared Function TicksPerYoctosecond() As Decimal
Return Stopwatch.Frequency / 1000000000000000000000000D
End Function
Public Shared Function TicksPerZeptosecond() As Decimal
Return Stopwatch.Frequency / 1000000000000000000000D
End Function
Public Shared Function TicksPerAttosecond() As Decimal
Return Stopwatch.Frequency / 1000000000000000000D
End Function
Public Shared Function TicksPerFemtosecond() As Decimal
Return Stopwatch.Frequency / 1000000000000000D
End Function
Public Shared Function TicksPerPicosecond() As Decimal
Return Stopwatch.Frequency / 1000000000000D
End Function
Public Shared Function TicksPerNanosecond() As Decimal
Return Stopwatch.Frequency / 1000000000D
End Function
Public Shared Function TicksPerMicrosecond() As Decimal
Return Stopwatch.Frequency / 1000000D
End Function
Private Shared _TicksPerMillisecond As Decimal = Stopwatch.Frequency / 1000D
Public Shared Function TicksPerMillisecond() As Decimal
Return _TicksPerMillisecond
End Function
Public Shared Function TicksPerSecond() As Decimal
Return Stopwatch.Frequency
End Function
Public Shared Function TicksPerMinute() As Decimal
Return Stopwatch.Frequency * 60D
End Function
Public Shared Function TicksPerHour() As Decimal
Return Stopwatch.Frequency * 3600D
End Function
Public Shared Function TicksPerDay() As Decimal
Return Stopwatch.Frequency * 86400D
End Function
Public Shared Function TicksPerWeek() As Decimal
Return Stopwatch.Frequency * 604800D
End Function
Public Shared Function TicksPerMonth() As Decimal
Return Stopwatch.Frequency * 2592000D
End Function
Public Shared Function TicksPerYear() As Decimal
Return Stopwatch.Frequency * 31536000D
End Function
#End Region
<DllImport("winmm.dll")>
Private Shared Function timeBeginPeriod(ByVal msec As UInteger) As MMRESULT
End Function
<DllImport("winmm.dll")>
Private Shared Function timeEndPeriod(ByVal msec As UInteger) As MMRESULT
End Function
<DllImport("winmm.dll")>
Private Shared Function timeSetEvent(ByVal delay As UInteger, ByVal resolution As UInteger,
ByVal handler As TimerEventDel, ByVal user As IntPtr,
ByVal eventType As UInteger) As MMRESULT
End Function
<DllImport("winmm.dll")>
Private Shared Function timeKillEvent(ByVal id As UInteger) As MMRESULT
End Function
Private Delegate Sub TimerEventDel(ByVal id As UInteger,
ByVal msg As UInteger,
ByVal user As IntPtr,
ByVal dw1 As IntPtr,
ByVal dw2 As IntPtr)
Private Shared mHandler As New TimerEventDel(Sub(ByVal id As UInteger,
ByVal msg As UInteger,
ByVal user As IntPtr,
ByVal dw1 As IntPtr,
ByVal dw2 As IntPtr)
timeKillEvent(id)
wh.Set()
End Sub)
Private Shared wh As New AutoResetEvent(False)
Private Shared wThread As Thread = Nothing
Private Shared I As Integer = 0
Private Shared NextTime As Decimal
Private Shared Mode As Integer
Private Shared _TicksPerInterval As Decimal
Public Enum TimerMode As Integer
Poll = -1
Efficient = 0
End Enum
Private Enum MMRESULT
MMSYSERR_NOERROR = 0
MMSYSERR_ERROR = 1
MMSYSERR_BADDEVICEID = 2
MMSYSERR_NOTENABLED = 3
MMSYSERR_ALLOCATED = 4
MMSYSERR_INVALHANDLE = 5
MMSYSERR_NODRIVER = 6
MMSYSERR_NOMEM = 7
MMSYSERR_NOTSUPPORTED = 8
MMSYSERR_BADERRNUM = 9
MMSYSERR_INVALFLAG = 10
MMSYSERR_INVALPARAM = 11
MMSYSERR_HANDLEBUSY = 12
MMSYSERR_INVALIDALIAS = 13
MMSYSERR_BADDB = 14
MMSYSERR_KEYNOTFOUND = 15
MMSYSERR_READERROR = 16
MMSYSERR_WRITEERROR = 17
MMSYSERR_DELETEERROR = 18
MMSYSERR_VALNOTFOUND = 19
MMSYSERR_NODRIVERCB = 20
WAVERR_BADFORMAT = 32
WAVERR_STILLPLAYING = 33
WAVERR_UNPREPARED = 34
End Enum
Public Shared Sub SetStart(ByVal Ticks As Long, Optional ByVal Mode As TimerMode = TimerMode.Efficient)
If Ticks > 0 Then
Interlocked.Exchange(_TicksPerInterval, Ticks)
Interlocked.Exchange(MicroTimer.Mode, Mode)
If wThread Is Nothing Then CreateStartThread()
End If
End Sub
Public Shared Sub SetStart(ByVal Ticks As Long, ByVal Precision As Integer)
If Precision < 0 Then Throw New ArgumentOutOfRangeException(Precision, "Value must be of integer type greater than or equal to 0")
If Ticks > 0 Then
Interlocked.Exchange(_TicksPerInterval, Ticks)
Interlocked.Exchange(Mode, Precision)
If wThread Is Nothing Then CreateStartThread()
End If
End Sub
Public Shared Sub SetStart(ByVal Rate As Decimal, Optional ByVal Mode As TimerMode = TimerMode.Efficient)
If Rate > 0 Then
Interlocked.Exchange(_TicksPerInterval, Stopwatch.Frequency / Rate)
Interlocked.Exchange(MicroTimer.Mode, Mode)
If wThread Is Nothing Then CreateStartThread()
End If
End Sub
Public Shared Sub SetStart(ByVal Rate As Decimal, ByVal Precision As Integer)
If Precision < 0 Then Throw New ArgumentOutOfRangeException(Precision, "Value must be of integer type greater than or equal to 0")
If Rate > 0 Then
Interlocked.Exchange(_TicksPerInterval, Stopwatch.Frequency / Rate)
Interlocked.Exchange(Mode, Precision)
If wThread Is Nothing Then CreateStartThread()
End If
End Sub
Private Shared Sub CreateStartThread()
wThread = New Thread(Sub()
NextTime = Stopwatch.GetTimestamp
While True
If Mode > -1 Then
If Mode = TimerMode.Efficient _
Then I = (NextTime - Stopwatch.GetTimestamp) / _TicksPerMillisecond _
Else I = Math.Round(((NextTime - Stopwatch.GetTimestamp) / _TicksPerMillisecond) - Mode)
If I > 1 Then
timeSetEvent(I, 0, mHandler, IntPtr.Zero, 0)
wh.WaitOne()
End If
End If
While Stopwatch.GetTimestamp < NextTime : End While
Do
RaiseEvent Elapsed()
NextTime += GetTicksPerInterval()
Loop While NextTime < Stopwatch.GetTimestamp
End While
End Sub) With {.Priority = ThreadPriority.Highest, .IsBackground = True}
wThread.Start()
End Sub
Public Shared Function GetTicksPerInterval() As Decimal
Return _TicksPerInterval
End Function
End Class
The reason initially why I didn't paste the source is because I thought that if I ever wanted to make changes I only had to edit from one location but I guess you have a point with the potential reliability of pastebin or lack thereof.
Last edited by TizzyT; Mar 22nd, 2017 at 12:25 PM.
Tags for this Thread
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
|