Results 1 to 10 of 10

Thread: MicroTimer, Resource Friendly Accurate Timer

  1. #1

    Thread Starter
    Member
    Join Date
    Oct 2016
    Posts
    32

    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.

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

    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

  3. #3
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    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.

  4. #4

    Thread Starter
    Member
    Join Date
    Oct 2016
    Posts
    32

    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.

  5. #5
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    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.

  6. #6

    Thread Starter
    Member
    Join Date
    Oct 2016
    Posts
    32

    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

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

    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

  8. #8

    Thread Starter
    Member
    Join Date
    Oct 2016
    Posts
    32

    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.

  9. #9
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    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.

  10. #10

    Thread Starter
    Member
    Join Date
    Oct 2016
    Posts
    32

    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
  •  



Click Here to Expand Forum to Full Width