虽然是C++的文章,不过我觉得对了解多线程在底层的运行情况有帮助,就转来了。
你想知道你的代码究竟执行了多长时间吗?是的,有的时候我们需要确定我们的代码到底执行了多长时间。或许你会想这有何难,在代码的前后增加GetTickCount来得到时间不就可以了吗?没错,如果系统是线形执行的话,这样做肯定可以得到,但我们怎么能够保证系统是线形的来?
我们这里只讨论WINDOWS,WINDOWS是典型的多线程、抢占式的多任务操作系统。如果你的计算机只有一块CPU的话,那么你的应用程序是这样运行的:一个线程执行一个时间片,然后再由另外一个线程执行一个时间片(这个时间片是20毫秒),然后又可能暂停这个线程回到开始的哪个线程,也有可能执行其他的线程;这就是操作系统的调度算法了,调度算法是复杂的,目的是保证所有的线程都有机会被执行,从而看起来感觉每个线程都在运行,但实际上每个时刻只可能有一个线程在运行,即占用CPU时间(所以线程是分配CPU资源的最小单位了)。上面的是单CPU的,如果在多CPU上就更复杂了,但这个时候就不能保证一个时刻只有一个线程被执行了,因为可能在每个CPU上运行一个线程(WINDOWS2000就可以在多个CPU上运行),从而更好的保证了程序的并行性。
从我们对WINDOWS的分析可以看出,程序不是线性方式执行的。或许你会问:单线程、单CPU的情况下,程序是线性的吧?乍听起来说的有道理,但事实并不是这样,因为你的程序运行在操作系统上,操作系统要能够正确的运行,它本身就有许多线程在运行(EXPLORE.EXE),在必要的时候被唤醒,所以你的程序根本不可能在单线程的理想环境下运行。
好了,既然我们已经了解了上面的本质之后,你就会想,是的真的没有办法来得到我的代码到底执行了多长时间?其实不然,下面我们就几种测试方法来比较一下,究竟该如何计算?
用来计算代码执行时间有三种方法:
1. 使用GetTickCount,得到当前的时间,单位是毫秒
代码示例:
DWORD startTime = GetTickCount();
//被测试的代码
DWORD totalTime = GetTtickCount() – startTime;
2. 使用GetThreadTimes;该函数得到的时间包括两部分,内核执行的时间和用户代码的执行时间。
代码示例:
FILETIME m_ftKernelTimeStart;
FILETIME m_ftKernelTimeEnd;
FILETIME m_ftUserTimeStart;
FILETIME m_ftUserTimeEnd;
GetThreadTimes(GetCurrentThread(),&m_ftDummy,&m_ftDummy,
&m_ftKernelTimeStart,&m_ftUserTimeStart);
//被测试的代码
GetThreadTimes(GetCurrentThread(),&m_ftDummy,&m_ftDummy,
& m_ftKernelTimeEnd,& m_ftUserTimeEnd);
使用GetThreadTimes得到的时间单位也是毫秒级别的
3. 使用QueryPerformanceCounter
代码示例:
LARGE_INTEGER m_liPerfFreq;
LARGE_INTEGER m_liPerfStart;
QueryPerformanceFrequency(&m_liPerfFreq);
QueryPerformanceCounter(&m_liPerfStart);
//被测试的代码
LARGE_INTEGER liPerfNow;
QueryPerformanceCounter(&liPerfNow);
__int64 totalTime = (((liPerfNow.QuadPart- m_liPerfStart.QuadPart)*1000000)
/m_liPerfFreq.QuadPart);
使用第一种方法的缺点我们已经说过了,它假定你的程序不被打断的执行,这样才能得到正确的时间,而这种情况在一个同时运行了其他程序的环境下几乎是不存在的,所以这个方式的误差最大,得到的值也没有什么规律,如果使用第一种方法,最好要多次测试,取平均值是有帮助的。
使用第二种方法一般情况下可以得到线程的执行时间,该函数被设计就是用来得到线程得到了多少CPU时间,所以相对于第一种方法就准确的多;但我在多次测试的时候发现,使用该方法在测试比较短的时间(小于1毫秒)时会出现一些奇怪的现象,你可能多次得到一个0的结果,但突然得到一个15ms这样的测试结果(到几天我也没有明白这个时间是怎么计算的)。
使用第三种方法的要求和第一种方法一样,它也要求你的程序不被打断,但使用该函数可以得到更精确的时间(100纳秒为单位),所以它也是有用武之地的。我们前面说过WINDOWS调度线程是按时间片来调度,这个时间片是20ms,因此如果你的代码执行时间在20ms内,那么我们就有很大的可能得到正确的时间了;例如:你的代码需要执行20微秒,那么你这样编写代码:
Int I =0;
While(i<2000)
{
QueryPerformance
//被测试的代码
QueryPerformance
}
你会惊奇的发现,你的那段被测试代码执行时间是:
20,20,20,…….,1000,20,20,20……..9000,20,20,….
这种现象的原因是你的这段代码整个时间超过了20ms这个时间片,所以在中间的某次执行时,你的线程被暂停去执行别的代码去了,然后又回来,这个时候就将别的代码执行时间作为你这段测试代码的执行时间了(啊,真冤枉啊,其实我执行的很快—被测试代码说)。
好了我们基本知道该如何得到我们代码的执行时间,同样我们没有办法非常准确的得到我们代码执行了多长时间,如果你想使你的代码尽量不被打断,那么你可以试着将你的线程的优先级设置高一些(理论上是线程优先级越高得到CPU的时间就越多,但还是不能保证你的线程不被打断,而且我在测试的时候效果也不明显)。