dotnet下时间测量(续):进行纳秒级测量

还能不能得到更精确的时间呢?那就用汇编吧,通过rdtsc直接取时钟周期数。在Feng Yuan的《Windows图形编程》上找到获取时钟周期的函数,在网上搜索到获取本机CPU主频函数,凑在一起,得到如下代码:

 1  #include  < windows.h >
 2 
 3  extern   " C "
 4  {
 5      __declspec(dllexport) unsigned __int64 GetCycleCount( void )
 6      {
 7          _asm    _emit  0x0F ;
 8          _asm    _emit  0x31 ;
 9      }
10 
11      __declspec(dllexport)  int  GetFrequency( void // MHz
12      {
13          LARGE_INTEGER CurrTicks, TicksCount;
14          __int64 iStartCounter, iStopCounter;
15 
16          DWORD dwOldProcessP  =  GetPriorityClass(GetCurrentProcess());
17          DWORD dwOldThreadP  =  GetThreadPriority(GetCurrentThread());
18 
19          SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
20          SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
21 
22          QueryPerformanceFrequency( & TicksCount);
23          QueryPerformanceCounter( & CurrTicks);
24 
25          TicksCount.QuadPart  /=   16 ;
26          TicksCount.QuadPart  +=  CurrTicks.QuadPart;
27 
28          _asm rdtsc
29          _asm mov DWORD PTR iStartCounter, EAX
30          _asm mov DWORD PTR (iStartCounter + 4 ), EDX
31 
32           while (CurrTicks.QuadPart < TicksCount.QuadPart)
33              QueryPerformanceCounter( & CurrTicks);
34 
35          _asm rdtsc
36          _asm mov DWORD PTR iStopCounter, EAX
37          _asm mov DWORD PTR (iStopCounter  +   4 ), EDX
38 
39          SetThreadPriority(GetCurrentThread(), dwOldThreadP);
40          SetPriorityClass(GetCurrentProcess(), dwOldProcessP);
41 
42           return  ( int )((iStopCounter - iStartCounter) / 62500 );
43      }
44  }

编译为TimeStamp.dll,通过DllImport调用,代码如下(为测试而作,实用得再加几个方法):

1 None.gif      public   sealed   class  TimeStamp
2 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
3InBlock.gif        [DllImport("TimeStamp.dll")]
4InBlock.gif        public static extern long GetCycleCount();
5InBlock.gif
6InBlock.gif        [DllImport("TimeStamp.dll")]
7InBlock.gif        public static extern int GetFrequency();
8ExpandedBlockEnd.gif    }

先测试TimeStamp.GetCycleCount()的执行时间,也既两次GetCycleCount()运行的时间差。测试代码如下:

 1 None.gif          static   void  TestAbsoluteError()
 2 ExpandedBlockStart.gifContractedBlock.gif         dot.gif {
 3InBlock.gif            long start,end;
 4InBlock.gif            int frequency = TimeStamp.GetFrequency();
 5InBlock.gif
 6InBlock.gif            using (StreamWriter sw = new StreamWriter("Results.txt")) 
 7ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 8InBlock.gif
 9InBlock.gif                for(int i=0;i<1000;i++)
10ExpandedSubBlockStart.gifContractedSubBlock.gif                dot.gif{
11InBlock.gif                    start=TimeStamp.GetCycleCount();
12InBlock.gif                    end=TimeStamp.GetCycleCount();
13InBlock.gif                    sw.WriteLine("{0}",(double)(end-start)/frequency);
14ExpandedSubBlockEnd.gif                }

15ExpandedSubBlockEnd.gif            }

16ExpandedBlockEnd.gif        }


作统计图:

image-1.jpg

中间有几个10ms的突出点,这应该是测试程序被中断了。

为了避免被外界中断,把当前进程设置为实时进程,所得结果作图如下。

image-2.jpg

可以看出,i>50后,结果稳定在0.1646微秒。也就是说,必须至少运行100次TimeStamp.GetCycleCount(),然后每次TimeStamp.GetCycleCount()调用的时间才会降低到0.1646微秒,所得的测试结果才比较精确。为安全起见,建议每次测试之前运行至少500次TimeStamp.GetCycleCount()。

比较实时进程和非实时进程运行的前100次结果,得图:

image-3.jpg

可以看出,开始一次TimeStamp.GetCycleCount()调用运行耗时差不多都在2-3微秒,当i在50附近,非实时进程测试中这一值跳到3-4,然后迅速下降到0.164,实时进程跳到>25,然后下降到0.164。这应该和运行时优化有关。

为了得到更精确的时间,需要对测试结果进行修正,扣除一次TimeStamp.GetCycleCount()调用的时间。测量修正效果,代码如下:

 1 None.gif
 2 None.gif         static   void  TestRelativeError()
 3 ExpandedBlockStart.gifContractedBlock.gif         dot.gif {
 4InBlock.gif            long start,end;
 5InBlock.gif            int frequency = TimeStamp.GetFrequency();
 6InBlock.gif            long check=0;
 7InBlock.gif
 8InBlock.gif            using (StreamWriter sw = new StreamWriter("Results.txt")) 
 9ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
10InBlock.gif                for(int i=0;i<500;i++)
11ExpandedSubBlockStart.gifContractedSubBlock.gif                dot.gif{
12InBlock.gif                    TimeStamp.GetCycleCount();
13ExpandedSubBlockEnd.gif                }

14InBlock.gif
15InBlock.gif                for(int i=0;i<500;i++)
16ExpandedSubBlockStart.gifContractedSubBlock.gif                dot.gif{
17InBlock.gif                    start=TimeStamp.GetCycleCount();
18InBlock.gif                    end=TimeStamp.GetCycleCount();
19InBlock.gif                    check+=end-start;
20ExpandedSubBlockEnd.gif                }

21InBlock.gif                check = check/500;
22InBlock.gif
23InBlock.gif                Console.WriteLine(check);
24InBlock.gif
25InBlock.gif                for(int i=0;i<10000;i++)
26ExpandedSubBlockStart.gifContractedSubBlock.gif                dot.gif{
27InBlock.gif                    start=TimeStamp.GetCycleCount();
28InBlock.gif                    end=TimeStamp.GetCycleCount();
29InBlock.gif                    sw.WriteLine("{0}",(double)(end-start-check)/frequency);
30ExpandedSubBlockEnd.gif                }

31ExpandedSubBlockEnd.gif            }

32ExpandedBlockEnd.gif        }


得到下图:

image-4.jpg

可以看出,大多数情况下,修正后时间测量误差是-0.0006微秒,也就是-0.6纳秒,一个时钟周期左右!!!!够精确了!!!!!!!!!考虑极少数异常现象,误差也在20纳秒之内,但这些异常点可以通过多次测试去除最高最低点排除在外。

总结:

通过调用rdtsc,并经过时间校正,可以得到纳米级的时间测量工具。

具体步骤如下:

(1)设置运行进程为实时进程;

(2)运行至少100次(最好500次)TimeStamp.GetCycleCount()热热身;

(3)运行500次左右TimeStamp.GetCycleCount()得到TimeStamp.GetCycleCount()调用平均周期数;

(4)进行时间测量,测量结果减去第(3)步中的调用时间

(5)重复第4步n次(n>5)既可,去掉一个最高的,去掉一个最低的,剩下的取平均值,这样测量误差应该小于1纳秒。

转载于:https://www.cnblogs.com/xiaotie/archive/2005/08/17/216876.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值