• 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    public sealed class TimeStamp
    2    {
    3        [DllImport("TimeStamp.dll")]
    4        public static extern long GetCycleCount();
    5
    6        [DllImport("TimeStamp.dll")]
    7        public static extern int GetFrequency();
    8    }

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

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

    15            }

    16        }


    作统计图:



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

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



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

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



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

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

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

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

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

    31            }

    32        }


    得到下图:



    可以看出,大多数情况下,修正后时间测量误差是-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纳秒。

    版权所有,欢迎转载
  • 相关阅读:
    Quartz学习——Spring和Quartz集成详解(三)
    Quartz学习——Quartz简单入门Demo(二)
    Quartz学习——Quartz大致介绍(一)
    Quartz学习——SSMM(Spring+SpringMVC+Mybatis+Mysql)和Quartz集成详解(四)
    使用spring+quartz配置多个定时任务
    quartz定时任务时间设置
    Hibernate事务与并发问题处理(乐观锁与悲观锁)
    Oracle体系结构之参数文件管理
    Oracle体系结构之密码文件管理
    LINUX RHEL AS 4 + ORACLE10G安装详解
  • 原文地址:https://www.cnblogs.com/xiaotie/p/216876.html
Copyright © 2020-2023  润新知