Cross-platform high resolution tick counter on Mon

2019-07-18 12:36发布

问题:

I'm looking for a high resolution tick counter on Mono, preferably about the same resolution as a QueryPerformanceCounter on Win32/.NET.

Is this something that needs to be implemented as a native call (like QueryPerformanceCounter is on .NET/Win32) on each platform I need to support? (Linux, OSX).

I need about <1ms resolution.

回答1:

You should use System.Diagnostics.Stopwatch for this.



回答2:

See my answer here: https://stackoverflow.com/a/37882723/5073786

using Mono.Unix.Native;
namespace drone.StackOverflow{

  internal class LinuxHiResTimer {
    internal event EventHandler Tick; // Tick event 

    private System.Diagnostics.Stopwatch watch; // High resolution time
    const uint safeDelay = 0; // millisecond (for slightly early wakeup)
    private Timespec pendingNanosleepParams = new Timespec();
    private Timespec threadNanosleepParams = new Timespec();
    object lockObject = new object();
    internal long Interval { 
        get{
            double totalNanoseconds;
            lock (lockObject) {
                totalNanoseconds= (1e9 * pendingNanosleepParams.tv_sec)
                                         + pendingNanosleepParams.tv_nsec; 


            }
            return (int)(totalNanoseconds * 1e-6);//return value in ms
        } 
        set{
            lock (lockObject) {
                pendingNanosleepParams.tv_sec = value / 1000;
                pendingNanosleepParams.tv_nsec = (long)((value % 1000) * 1e6);//set value in ns
            }
        }
    }
    private bool enabled;
    internal bool Enabled {
        get { return enabled; }
        set {
            if (value) {
                watch.Start();
                enabled = value;
                Task.Run(()=>tickGenerator()); // fire up new thread
            }
            else {
                lock (lockObject) {
                    enabled = value;
                }
            }
        }

    }
    private Task tickGenerator() {
        bool bNotPendingStop; 
        lock (lockObject) {
            bNotPendingStop = enabled;
        }
        while (bNotPendingStop) {
            // Check if thread has been told to halt

            lock (lockObject) {
                bNotPendingStop = enabled;
            }
            long curTime = watch.ElapsedMilliseconds;
                if (curTime >= Interval) {
                    watch.Restart ();
                    if (Tick != null)
                        Tick (this, new EventArgs ());
                } else {
                    long iTimeLeft = (Interval - curTime); // How long to delay for 
                    if (iTimeLeft >= safeDelay) { // Task.Delay has resolution 15ms//await Task.Delay(TimeSpan.FromMilliseconds(iTimeLeft - safeDelay));
                        threadNanosleepParams.tv_nsec = (int)((iTimeLeft - safeDelay) * 1e6);
                        threadNanosleepParams.tv_sec = 0;
                        Syscall.nanosleep (ref threadNanosleepParams, ref threadNanosleepParams);
                    }
                }

        }
        watch.Stop();
        return null;
    }
}

Usage:

 private myMainFunction(){
  LinuxHiResTimer timReallyFast = new LinuxHiResTimer();
  timReallyFast.Interval=25; // 
  timReallyFast.Tick += new EventHandler(timReallyFast_Tick);
  timReallyFast.Enabled = true;
}
private void timReallyFast_Tick(System.Object sender, System.EventArgs e) {
// Do this quickly i.e. 
 PollSerialPort();
}


回答3:

And here is my answer here High resolution timer

https://gist.github.com/DraTeots/436019368d32007284f8a12f1ba0f545

  1. It works on all platforms (so no using Mono.Unix.Native;) and is high precision wherever StopWatch.IsHighPrecision == true

  2. Its Elapsed event is guaranteed to be non overlapping (which might be important to know, because state changes inside the event handler may be left unprotected against multi threaded access)

Here is how to use it:

Console.WriteLine($"IsHighResolution = {HighResolutionTimer.IsHighResolution}");
Console.WriteLine($"Tick time length = {HighResolutionTimer.TickLength} [ms]");

var timer = new HighResolutionTimer(0.5f);

// UseHighPriorityThread = true, sets the execution thread 
// to ThreadPriority.Highest.  It doesn't provide any precision gain
// in most of the cases and may do things worse for other threads. 
// It is suggested to do some studies before leaving it true
timer.UseHighPriorityThread = false;

timer.Elapsed += (s, e) => { /*... e.Delay */ }; // The call back
timer.Start();  
timer.Stop();    // by default Stop waits for thread.Join()
                 // which, if called not from Elapsed subscribers,
                 // would mean that all Elapsed subscribers
                 // are finished when the Stop function exits 
timer.Stop(joinThread:false)   // Use if you don't care and don't want to wait

Here is a benchmark (and a live example):
https://gist.github.com/DraTeots/5f454968ae84122b526651ad2d6ef2a3

The results of setting the timer for 0.5 ms on Windows 10:

It is also worth to mention that:

  1. I had the same precision on mono on Ubuntu.

  2. While playing with the benchmark, the maximum and a very rare deviation I saw was about 0.5 ms (which probably means nothing, it is not realtime systems, but still worth mentioning)

  3. Stopwatch ticks are not TimeSpan ticks. On that Windows 10 machine HighResolutionTimer.TickLength is 0.23[ns].