定时器function使用C ++提供纳秒时间
我希望计算一个API返回值的时间。 这种行动所花费的时间在纳秒之内。 由于API是一个C ++类/函数,我使用timer.h来计算相同的值:
#include <ctime> #include <cstdio> using namespace std; int main(int argc, char** argv) { clock_t start; double diff; start = clock(); diff = ( std::clock() - start ) / (double)CLOCKS_PER_SEC; cout<<"printf: "<< diff <<'\n'; return 0; }
上面的代码给出了几秒钟的时间。 如何在纳秒内获得相同的精度?
其他人发表的关于在循环中重复运行函数的内容是正确的。
对于Linux(和BSD)你想使用clock_gettime() 。
#include <sys/time.h> int main() { timespec ts; // clock_gettime(CLOCK_MONOTONIC, &ts); // Works on FreeBSD clock_gettime(CLOCK_REALTIME, &ts); // Works on Linux }
对于想要使用QueryPerformanceCounter的窗口。 QPC更多
显然,在某些芯片组上有一个QPC的问题 ,所以你可能要确保你没有这些芯片组。 另外一些双核心的AMD也可能会造成问题 。 请参阅sebbbi的第二篇文章,他在这里指出:
QueryPerformanceCounter()和QueryPerformanceFrequency()提供更好的解决scheme,但有不同的问题。 例如在Windows XP中,所有的AMD Athlon X2双核CPU都会“随机”返回任意一个核心的PC(PC往往有点跳跃),除非你特别安装了AMD双核驱动程序包来解决这个问题。 我们还没有注意到任何其他双核CPU有类似的问题(P4双,P4HT,Core2双核,Core2四核,Phenom四核)。
编辑2013/07/16:
在某些情况下,QPC的功效看起来有些争议,如http://msdn.microsoft.com/en-us/library/windows/desktop/ee417693(v=vs.85).aspx
虽然QueryPerformanceCounter和QueryPerformanceFrequency通常针对多个处理器进行调整,但BIOS或驱动程序中的错误可能导致这些例程在线程从一个处理器移动到另一个时返回不同的值。
但是,这个StackOverflow答案https://stackoverflow.com/a/4588605/34329指出,QPC应该在Win XP Service Pack 2之后在任何MS OS上正常工作。
本文显示,Windows 7可以确定处理器是否具有不变的TSC,如果不是,则返回到外部计时器。 http://performancebydesign.blogspot.com/2012/03/high-resolution-clocks-and-timers-for.html跨处理器同步仍然是一个问题。;
其他有关定时器的精美阅读:
- https://blogs.oracle.com/dholmes/entry/inside_the_hotspot_vm_clocks
- http://lwn.net/Articles/209101/
- http://performancebydesign.blogspot.com/2012/03/high-resolution-clocks-and-timers-for.html
- QueryPerformanceCounter状态?
请参阅评论了解更多详情。
这个新的答案使用C ++ 11的<chrono>
工具。 虽然还有其他答案显示如何使用<chrono>
,但是没有一个显示如何使用<chrono>
和其他几个答案中提到的RDTSC
工具。 所以我想我会展示如何用<chrono>
来使用RDTSC
。 另外,我将演示如何在时钟上testing代码,以便在RDTSC
和系统的内置时钟工具(可能基于clock()
, clock_gettime()
和/或QueryPerformanceCounter
clock_gettime()
之间快速切换。
请注意, RDTSC
指令是x86专用的。 QueryPerformanceCounter
仅适用于Windows。 而clock_gettime()
只是POSIX。 下面我介绍两个新的时钟: std::chrono::high_resolution_clock
和std::chrono::system_clock
,如果你可以假设C ++ 11,现在是跨平台的。
首先,这里是如何从Intel rdtsc
汇编指令中创build一个兼容C ++ 11的时钟。 我会叫它x::clock
:
#include <chrono> namespace x { struct clock { typedef unsigned long long rep; typedef std::ratio<1, 2'800'000'000> period; // My machine is 2.8 GHz typedef std::chrono::duration<rep, period> duration; typedef std::chrono::time_point<clock> time_point; static const bool is_steady = true; static time_point now() noexcept { unsigned lo, hi; asm volatile("rdtsc" : "=a" (lo), "=d" (hi)); return time_point(duration(static_cast<rep>(hi) << 32 | lo)); } }; } // x
所有这个时钟都会计算CPU周期并将其存储在一个无符号的64位整数中。 您可能需要调整编译器的汇编语言语法。 或者你的编译器可以提供一个你可以使用的内在(例如now() {return __rdtsc();}
)。
要build立一个时钟,你必须给它的表示(存储types)。 即使您的机器可能在不同的功耗模式下更改时钟速度,您也必须提供时钟周期,该周期必须是编译时间常数。 从这些基础知识来看,你可以很容易地定义你的时钟的“原生”时间和时间点。
如果你所要做的只是输出时钟周期的数量,那么在时钟周期中给出的数字并不重要。 如果要将时钟滴答数转换为某个实时单位(如纳秒),此常数才会起作用。 在这种情况下,您可以提供更精确的时钟速度,更准确的将转换为纳秒(毫秒,无论)。
下面是显示如何使用x::clock
示例代码。 其实我已经在时钟上的代码模板,因为我想展示如何使用许多不同的时钟完全相同的语法。 这个特定的testing显示了在循环中运行你想要的时间的循环开销:
#include <iostream> template <class clock> void test_empty_loop() { // Define real time units typedef std::chrono::duration<unsigned long long, std::pico> picoseconds; // or: // typedef std::chrono::nanoseconds nanoseconds; // Define double-based unit of clock tick typedef std::chrono::duration<double, typename clock::period> Cycle; using std::chrono::duration_cast; const int N = 100000000; // Do it auto t0 = clock::now(); for (int j = 0; j < N; ++j) asm volatile(""); auto t1 = clock::now(); // Get the clock ticks per iteration auto ticks_per_iter = Cycle(t1-t0)/N; std::cout << ticks_per_iter.count() << " clock ticks per iteration\n"; // Convert to real time units std::cout << duration_cast<picoseconds>(ticks_per_iter).count() << "ps per iteration\n"; }
这段代码所做的第一件事是创build一个“实时”单位来显示结果。我已经select了皮秒,但是你可以select你喜欢的任何单位,无论是基于整数还是浮点数。 作为一个例子,我可以使用一个预制的std::chrono::nanoseconds
单位。
作为另一个例子,我想把每次迭代的平均时钟周期数作为一个浮点数输出,所以我创build了另一个基于double的持续时间,它具有与时钟tick相同的单位(称为代码中的Cycle
)。
循环定时调用clock::now()
。 如果你想命名从这个函数返回的types是:
typename clock::time_point t0 = clock::now();
(如在x::clock
示例中清楚地显示的那样,系统提供的时钟也是如此)。
为了获得浮点时钟滴答的持续时间,仅仅减去两个时间点,并且获得每个迭代值,将该持续时间除以迭代次数。
您可以通过使用count()
成员函数来获得任何持续时间的count()
。 这将返回内部表示。 最后我使用std::chrono::duration_cast
将持续时间Cycle
转换为持续时间picoseconds
然后打印出来。
使用这段代码很简单:
int main() { std::cout << "\nUsing rdtsc:\n"; test_empty_loop<x::clock>(); std::cout << "\nUsing std::chrono::high_resolution_clock:\n"; test_empty_loop<std::chrono::high_resolution_clock>(); std::cout << "\nUsing std::chrono::system_clock:\n"; test_empty_loop<std::chrono::system_clock>(); }
上面我使用我们自制的x::clock
,并使用两个系统提供的时钟比较这些结果: std::chrono::high_resolution_clock
和std::chrono::system_clock
。 对我来说这打印出来:
Using rdtsc: 1.72632 clock ticks per iteration 616ps per iteration Using std::chrono::high_resolution_clock: 0.620105 clock ticks per iteration 620ps per iteration Using std::chrono::system_clock: 0.00062457 clock ticks per iteration 624ps per iteration
这表明每个时钟具有不同的滴答周期,因为每次迭代的滴答数对于每个时钟都是大不相同的。 但是,当转换为已知的时间单位(例如皮秒)时,每个时钟的结果都大致相同(您的里程可能会有所不同)。
注意我的代码是完全没有“魔术转换常量”的。 事实上,整个例子中只有两个幻数:
- 我的机器的时钟速度,以定义
x::clock
。 - 要testing的迭代次数。 如果改变这个数字会使你的结果差异很大,那么你应该使迭代次数更高,或者在testing时清空你的计算机的竞争进程。
有了这样的准确性,最好在CPU tick中进行推理,而不是像clock()那样在系统调用中进行推理。 不要忘记,如果执行一个指令需要超过一纳秒的时间,那么精确度达到纳秒级是几乎不可能的。
不过, 这样的事情是一个开始:
这是实际的代码来检索自CPU上次启动以来传递的80×86 CPU时钟滴答数。 它将在奔腾及更高版本上运行(不支持386/486)。 这段代码实际上是MS Visual C ++特有的,但是可以很容易地移植到其他任何地方,只要它支持内联汇编。
inline __int64 GetCpuClocks() { // Counter struct { int32 low, high; } counter; // Use RDTSC instruction to get clocks count __asm push EAX __asm push EDX __asm __emit 0fh __asm __emit 031h // RDTSC __asm mov counter.low, EAX __asm mov counter.high, EDX __asm pop EDX __asm pop EAX // Return result return *(__int64 *)(&counter); }
这个function还具有非常快的优势 – 通常不超过50个CPU周期执行。
使用时序图 :
如果您需要将时钟计数转换为真实的时间,请将结果除以芯片的时钟速度。 请记住,“额定”GHz可能会与芯片的实际速度略有不同。 要检查芯片的真实速度,可以使用几个非常好的实用程序或Win32调用QueryPerformanceFrequency()。
要正确地做到这一点,您可以使用以下两种方式之一,使用RDTSC
或使用clock_gettime()
。 第二个是大约2倍的速度,并有给予正确的绝对时间的优势。 请注意,为了使RDTSC
正常工作,您需要按照指示使用它(本页其他评论有错误,并且可能在某些处理器上产生不正确的计时值)
inline uint64_t rdtsc() { uint32_t lo, hi; __asm__ __volatile__ ( "xorl %%eax, %%eax\n" "cpuid\n" "rdtsc\n" : "=a" (lo), "=d" (hi) : : "%ebx", "%ecx" ); return (uint64_t)hi << 32 | lo; }
和clock_gettime :(我任意select微秒分辨率)
#include <time.h> #include <sys/timeb.h> // needs -lrt (real-time lib) // 1970-01-01 epoch UTC time, 1 mcs resolution (divide by 1M to get time_t) uint64_t ClockGetTime() { timespec ts; clock_gettime(CLOCK_REALTIME, &ts); return (uint64_t)ts.tv_sec * 1000000LL + (uint64_t)ts.tv_nsec / 1000LL; }
产生的时间和价值:
Absolute values: rdtsc = 4571567254267600 clock_gettime = 1278605535506855 Processing time: (10000000 runs) rdtsc = 2292547353 clock_gettime = 1031119636
我正在使用以下来获得所需的结果:
#include <time.h> #include <iostream> using namespace std; int main (int argc, char** argv) { // reset the clock timespec tS; tS.tv_sec = 0; tS.tv_nsec = 0; clock_settime(CLOCK_PROCESS_CPUTIME_ID, &tS); ... ... <code to check for the time to be put here> ... clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tS); cout << "Time taken is: " << tS.tv_sec << " " << tS.tv_nsec << endl; return 0; }
对于C ++ 11 ,这是一个简单的包装:
#include <iostream> #include <chrono> class Timer { public: Timer() : beg_(clock_::now()) {} void reset() { beg_ = clock_::now(); } double elapsed() const { return std::chrono::duration_cast<second_> (clock_::now() - beg_).count(); } private: typedef std::chrono::high_resolution_clock clock_; typedef std::chrono::duration<double, std::ratio<1> > second_; std::chrono::time_point<clock_> beg_; };
或者* nix上的C ++ 03,
class Timer { public: Timer() { clock_gettime(CLOCK_REALTIME, &beg_); } double elapsed() { clock_gettime(CLOCK_REALTIME, &end_); return end_.tv_sec - beg_.tv_sec + (end_.tv_nsec - beg_.tv_nsec) / 1000000000.; } void reset() { clock_gettime(CLOCK_REALTIME, &beg_); } private: timespec beg_, end_; };
使用示例:
int main() { Timer tmr; double t = tmr.elapsed(); std::cout << t << std::endl; tmr.reset(); t = tmr.elapsed(); std::cout << t << std::endl; return 0; }
一般来说,调用一个函数需要多长时间,你要做的不止一次。 如果你只调用一次你的函数,并且需要很短的时间才能运行,那么你仍然有实际调用定时器函数的开销,并且你不知道需要多长时间。
例如,如果你估计你的函数可能需要800纳秒运行,那么将其称为循环1000万次(这将花费约8秒)。 将总时间除以一千万,以获得每次通话的时间。
在x86处理器上运行的gcc可以使用以下函数:
unsigned long long rdtsc() { #define rdtsc(low, high) \ __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high)) unsigned int low, high; rdtsc(low, high); return ((ulonglong)high << 32) | low; }
与数字火星C ++:
unsigned long long rdtsc() { _asm { rdtsc } }
它读取芯片上的高性能定时器。 我在分析时使用这个。
使用Brock Adams的方法,用一个简单的类:
int get_cpu_ticks() { LARGE_INTEGER ticks; QueryPerformanceFrequency(&ticks); return ticks.LowPart; } __int64 get_cpu_clocks() { struct { int32 low, high; } counter; __asm cpuid __asm push EDX __asm rdtsc __asm mov counter.low, EAX __asm mov counter.high, EDX __asm pop EDX __asm pop EAX return *(__int64 *)(&counter); } class cbench { public: cbench(const char *desc_in) : desc(strdup(desc_in)), start(get_cpu_clocks()) { } ~cbench() { printf("%s took: %.4f ms\n", desc, (float)(get_cpu_clocks()-start)/get_cpu_ticks()); if(desc) free(desc); } private: char *desc; __int64 start; };
用法示例:
int main() { { cbench c("test"); ... code ... } return 0; }
结果:
testing花费了:0.0002毫秒
有一些函数调用开销,但应该还是比足够快:)
如果您需要亚秒级精度,则需要使用特定于系统的扩展,并且必须检查操作系统的文档。 POSIX支持微秒和gettimeofday ,但没有更精确的,因为电脑没有1GHz以上的频率。
如果你使用Boost,你可以检查boost :: posix_time 。
我在这里使用的Borland代码是代码ti_hund给了我一些negativnumber但时间是相当不错的。
#include <dos.h> void main() { struct time t; int Hour,Min,Sec,Hun; gettime(&t); Hour=t.ti_hour; Min=t.ti_min; Sec=t.ti_sec; Hun=t.ti_hund; printf("Start time is: %2d:%02d:%02d.%02d\n", t.ti_hour, t.ti_min, t.ti_sec, t.ti_hund); .... your code to time ... // read the time here remove Hours and min if the time is in sec gettime(&t); printf("\nTid Hour:%d Min:%d Sec:%d Hundreds:%d\n",t.ti_hour-Hour, t.ti_min-Min,t.ti_sec-Sec,t.ti_hund-Hun); printf("\n\nAlt Ferdig Press a Key\n\n"); getch(); } // end main
您可以使用Embedded Profiler (对于Windows和Linux免费),它具有多平台计时器的接口(处理器周期计数),并且可以每秒给出一些周期数:
EProfilerTimer timer; timer.Start(); ... // Your code here const uint64_t number_of_elapsed_cycles = timer.Stop(); const uint64_t nano_seconds_elapsed = mumber_of_elapsed_cycles / (double) timer.GetCyclesPerSecond() * 1000000000;
周期数重新计算时间可能是一个危险的操作与现代处理器CPU频率可以dynamic改变。 因此,为了确保转换的时间是正确的,有必要在分析之前修复处理器频率。
如果这是针对Linux的,我一直使用函数“gettimeofday”,它返回一个结构,它给出了Epoch以来的秒数和微秒数。 然后,您可以使用timersub来减去两者,以获得时间差异,并将其转换为您想要的任何时间精度。 但是,您指定纳秒,并且它看起来像函数clock_gettime()是您正在寻找。 它将时间以秒和纳秒的forms放入您传入的结构中。
你怎么看待这个问题:
int iceu_system_GetTimeNow(long long int *res) { static struct timespec buffer; // #ifdef __CYGWIN__ if (clock_gettime(CLOCK_REALTIME, &buffer)) return 1; #else if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &buffer)) return 1; #endif *res=(long long int)buffer.tv_sec * 1000000000LL + (long long int)buffer.tv_nsec; return 0; }
这是一个很好的升压计时器,效果很好:
//Stopwatch.hpp #ifndef STOPWATCH_HPP #define STOPWATCH_HPP //Boost #include <boost/chrono.hpp> //Std #include <cstdint> class Stopwatch { public: Stopwatch(); virtual ~Stopwatch(); void Restart(); std::uint64_t Get_elapsed_ns(); std::uint64_t Get_elapsed_us(); std::uint64_t Get_elapsed_ms(); std::uint64_t Get_elapsed_s(); private: boost::chrono::high_resolution_clock::time_point _start_time; }; #endif // STOPWATCH_HPP //Stopwatch.cpp #include "Stopwatch.hpp" Stopwatch::Stopwatch(): _start_time(boost::chrono::high_resolution_clock::now()) {} Stopwatch::~Stopwatch() {} void Stopwatch::Restart() { _start_time = boost::chrono::high_resolution_clock::now(); } std::uint64_t Stopwatch::Get_elapsed_ns() { boost::chrono::nanoseconds nano_s = boost::chrono::duration_cast<boost::chrono::nanoseconds>(boost::chrono::high_resolution_clock::now() - _start_time); return static_cast<std::uint64_t>(nano_s.count()); } std::uint64_t Stopwatch::Get_elapsed_us() { boost::chrono::microseconds micro_s = boost::chrono::duration_cast<boost::chrono::microseconds>(boost::chrono::high_resolution_clock::now() - _start_time); return static_cast<std::uint64_t>(micro_s.count()); } std::uint64_t Stopwatch::Get_elapsed_ms() { boost::chrono::milliseconds milli_s = boost::chrono::duration_cast<boost::chrono::milliseconds>(boost::chrono::high_resolution_clock::now() - _start_time); return static_cast<std::uint64_t>(milli_s.count()); } std::uint64_t Stopwatch::Get_elapsed_s() { boost::chrono::seconds sec = boost::chrono::duration_cast<boost::chrono::seconds>(boost::chrono::high_resolution_clock::now() - _start_time); return static_cast<std::uint64_t>(sec.count()); }
简约的复制和粘贴结构+懒惰的用法
如果想法有一个可以用来进行快速testing的简约结构,那么我build议你在#include
的后面复制并粘贴 C ++文件中的任意位置。 这是我牺牲Allman风格格式的唯一例子。
您可以轻松调整结构第一行的精度。 可能的值包括: nanoseconds
, microseconds
, milliseconds
, seconds
, minutes
或hours
。
#include <chrono> struct MeasureTime { using precision = std::chrono::microseconds; std::vector<std::chrono::steady_clock::time_point> times; std::chrono::steady_clock::time_point oneLast; void p() { std::cout << "Mark " << times.size()/2 << ": " << std::chrono::duration_cast<precision>(times.back() - oneLast).count() << std::endl; } void m() { oneLast = times.back(); times.push_back(std::chrono::steady_clock::now()); } void t() { m(); p(); m(); } MeasureTime() { times.push_back(std::chrono::steady_clock::now()); } };
用法
MeasureTime m; // first time is already in memory doFnc1(); mt(); // Mark 1: next time, and print difference with previous mark doFnc2(); mt(); // Mark 2: next time, and print difference with previous mark doStuff = doMoreStuff(); andDoItAgain = doStuff.aoeuaoeu(); mt(); // prints 'Mark 3: 123123' etc...
标准输出结果
Mark 1: 123 Mark 2: 32 Mark 3: 433234
如果你想在执行后汇总
如果你想要之后的报告,因为例如你之间的代码也写到标准输出。 然后将下面的函数添加到结构中(就在MeasureTime()之前):
void s() { // summary int i = 0; std::chrono::steady_clock::time_point tprev; for(auto tcur : times) { if(i > 0) { std::cout << "Mark " << i << ": " << std::chrono::duration_cast<precision>(tprev - tcur).count() << std::endl; } tprev = tcur; ++i; } }
那么你可以使用:
MeasureTime m; doFnc1(); mm(); doFnc2(); mm(); doStuff = doMoreStuff(); andDoItAgain = doStuff.aoeuaoeu(); mm(); ms();
其中将像以前一样列出所有标记,但在执行其他代码之后。 请注意,您不应该同时使用ms()
和mt()
。