关于高精度计时器

一、毫秒级精度

    1、[.NET] System.Environment.TickCount

       获取系统启动后经过的毫秒数,包装了GetTickCount

    2、[WINAPI] GetTickCount 

[DllImport("kernel32")]
static extern uint GetTickCount();

        从操作系统启动到现在所经过的毫秒数,精度为1毫秒,经简单测试发现其实误差在大约在15ms左右

    3、[WINAPI] timeGetTime 

[DllImport("winmm")]
static extern uint timeGetTime();

      常用于多媒体定时器中,与GetTickCount类似,也是返回操作系统启动到现在所经过的毫秒数,最高精度为1毫秒。一般默认的精度不止1毫秒(在NT系统上据说默认精度为10ms,但是可以用timeBeginPeriod来降低到1ms),需要调用timeBeginPeriod与timeEndPeriod来设置精度

二、微秒级精度

    1、[.NET] System.Diagnostics.Stopwatch

        实际上它里面就是将QueryPerformanceCounter、QueryPerformanceFrequency两个WIN API封装了一下,如果硬件支持高精度,就调用QueryPerformanceCounter,如果不支持就用DateTime.Ticks来计算。

    2、[WINAPI]QueryPerformanceCounterQueryPerformanceFrequency

[DllImport("kernel32.dll ")]
static extern bool QueryPerformanceCounter(ref long tick);
[DllImport("kernel32.dll ")]
static extern bool QueryPerformanceFrequency(ref long tick);

      QueryPerformanceCounter用于得到高精度计时器(如果存在这样的计时器)的值。如果安装的硬件不支持高精度计时器,函数将返回false。

        QueryPerformanceFrequency返回硬件支持的高精度计数器的频率,如果安装的硬件不支持高精度计时器,函数将返回false。

        但是据说该API在节能模式的时候结果偏慢,超频模式的时候又偏快,而且用电池和接电源的时候效果还不一样(笔记本)。没有测试过。

二、纳秒级精度

    [ASM] rdtsc

    机器码:0x0F, 0x31

    C++中使用方法:

inline __declspec(naked) unsigned long Tick()
{
	_asm _EMIT 0x0F;
	_asm _EMIT 0x31;
	_asm ret;
}

    C#中使用方法(C#中内联汇编):

static class Helper
{
    [DllImport("kernel32.dll")]
    static extern int VirtualProtect(IntPtr lpAddress, int dwSize, int flNewProtect, ref int lpflOldProtect);
    delegate ulong GetTickDelegate();
    static readonly IntPtr Addr;
    static readonly GetTickDelegate getTick;
    static readonly byte[] asm = { 0x0F, 0x31, 0xC3 };//rdtsc, ret的机器码
    static Helper()
    {
        Addr = Marshal.AllocHGlobal(3);
        int old = 0;
        VirtualProtect(Addr, 3, 0x40, ref old);
        Marshal.Copy(asm, 0, Addr, 3);
        getTick = (GetTickDelegate)Marshal.GetDelegateForFunctionPointer(Addr, typeof(GetTickDelegate));
    }

    public static ulong Tick
    {
        get
        {
            return getTick();
        }
    }
}

  调用:Helper.Tick

以下是百度看到的:

这个指令在超线程和多核CPU上用来计算时间不是很准确

    1 根据intel的介绍,由于在现代的处理器中都具有指令乱序执行的功能,因此在有些情况下rdtsc指令并不能很好的反映真实情况。解决方法是,在rdtsc之前加一些cpuid指令,使得rdtsc后面的指令顺序执行。

    2 另外,rdtsc是一条慢启动的指令,第一次执行需要比较长的启动时间,而第二次之后时间就比较短了,也就是说,这条指令在第一次工作时需要比较长的时钟周期,之后就会比较短了。所以可以多运行几次,避过第一次的消耗。

    3 大家在测试某一个函数的cpu周期的时候,如果精度要求很高,需要减去rdtsc的周期消耗。我在至强2.6G上测试的结果是大约500多个时钟周期,我想这是应该考虑在内的,很多小的函数也就是几K个时钟周期。

    4 一定要注意cache的影响。如果你在对同一组数据进行操作,第一次操作往往要比后面几次时间开销大,原因就在于cache的缓存功能,而这一部分是不可见的。

原文地址:https://www.cnblogs.com/wmesci/p/2736010.html