一、毫秒级精度
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]QueryPerformanceCounter、QueryPerformanceFrequency
[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的缓存功能,而这一部分是不可见的。