我在VC ++ 2013,Windows 7-64,Intel i7 3.6 GHz。
我想测量非常快速的数学运算的执行时间,例如我希望将标准fabsf()
函数的性能与备用“更快”方法或标准tanh()
与Pade近似进行比较等等。
问题是这些操作太快了,即使我运行它们数十万次,我总是在基准测试的结束和开始之间获得0毫秒。
我尝试使用<chrono>
获得以纳秒为单位的时间,但是它被舍入到十分之一毫秒,而不是真正的纳秒,所以在我的基准测试中我仍然得到0纳秒。
您能否提供一些我可用于运行基准测试的代码片段?
这是我的:
#include <vector>
#include <chrono>
#include <ctime>
using namespace std;
// 1/RAND_MAX
#define RAND_MAX_RECIP 0.00003051757f
int _tmain(int argc, _TCHAR* argv[])
{
srand (static_cast <unsigned> (time(0)));
// Fill a buffer with random float numbers
vector<float> buffer;
for (unsigned long i=0; i<10000000; ++i)
buffer.push_back( (float)rand() * RAND_MAX_RECIP );
// Get start time
auto start = std::chrono::high_resolution_clock::now();
for (unsigned long i=0; i<buffer.size(); ++i)
{
// do something with the float numbers in the buffer
}
// Get elapsed time
auto finish = std::chrono::high_resolution_clock::now();
printf("Executed in %d ns\n\n", std::chrono::duration_cast<std::chrono::nanoseconds>(finish-start).count());
return 0;
}
答案 0 :(得分:6)
我认为最可能的问题是编译器注意到您没有使用计算结果并优化计算。你只需要说服编译器不要这样做。
我建议只保留所有计算结果的运行总和,并在打印完循环所需的时间后将其打印出来。你忽略了最后的总和,但是编译器不知道。
答案 1 :(得分:5)
为了防止Jens提到的问题,您必须使用结果。为了解决无论我设置计数器多少次的问题,时间总是0,你采取另一种方法。运行该操作1秒钟并计算处理的次数。
Psuedo代码是
double TestFunc()
{
double dSum=0, dForce=0;
while(run)
{
// do test and keep the result
// dForce += fabs(v); // whatever v is - just keep the result
dSum +=1;
}
printf("anchor answer is "+dForce) ;// this forces the compiler to generate code
return dSum;
}
然后运行该代码1秒钟,或者多长时间。
然后诀窍是在没有测试代码的情况下运行相同的循环,看看它迭代了多少次。然后,您从第二个数字中减去第一个数字,看看您的代码(单独)花了多长时间。
答案 2 :(得分:4)
直接映射到指令的fabs()
等函数很难在综合基准测试中进行评估,因为与管道延迟,内存访问时间等相比,它们的执行时间非常短。例如,如果你有一个循环从数组中读取一个float,找到它的绝对值,然后将值写回数组,在循环中执行第二个fabs()
可能不会对执行时间产生影响 - 算法将受内存限制,而不受CPU限制。
出于同样的原因,使用单个数字来衡量fabs
之类的操作的“速度”是很困难的。特别是对于某些多发和无序处理器,执行此类操作所花费的时间在很大程度上取决于在其之前和之后执行的其他操作。
你应该看一下关于x86 / x64指令时间的Agner Fog的页面,以了解所涉及的细微差别。至于实用性,不要费心去尝试单一操作。尝试计算您实际想要使用该操作的算法。如果存在差异,您知道要使用哪个算法,并且您知道该选择是针对您的特定用例正确地进行了上下文化的。如果没有显着差异(我猜不会有),那么你知道这没关系。
答案 3 :(得分:3)
您可以使用rdtsc
指令在时钟周期级别计时。
uint64_t begin = __rdtsc();
_mm_lfence();
// insert your code here
_mm_lfence();
uint64_t end = __rdtsc();
uint64_t clocks = end - begin;
围栏是为了避免重新安排说明。
时间几十万次并取中值。以下陷阱适用:
rdtsc
始终根据处理器的基准时钟进行滴答。我通常会使用throttlestop。cmov
(条件移动)指令。DIV
或分支,或从内存中读取的任何内容。您可能希望使用http://agner.org/optimize/#testp在指令级别运行基准测试。
答案 4 :(得分:1)
此类基准的一般策略是:
你会发现编译器在跳过没有用处的循环时非常狡猾。在编译器生成所需的序列之前,禁用优化和/或使代码更复杂。
仔细观察管道和缓存效果。除非或直到您可以通过多种策略获得多次重复的完全匹配答案,否则您无法依赖结果。这是实验计算机科学,而且很难。