您如何对功能进行基准测试?查看来自callgrind的结果,我发现我的程序在pow
中花了很多时间。由于我不需要完全的工作精度,我认为我可以创建一个查找表并在表格中的点之间使用线性插值。为了能够评估查找表方法,我需要测量时间。所以我这样做了:
#ifdef __WAND__
target[name[test2.exe] type[application] platform[;Windows]]
target[name[test2] type[application]]
#endif
#include <herbs/main/main.h>
#include <herbs/tictoc/tictoc.h>
#include <herbs/array_fixedsize/array_fixedsize.h>
#include <random>
#include <cstdio>
#include <cmath>
class GetRand
{
public:
GetRand(double min,double max):U(min,max){}
bool operator()(double* val,size_t n,size_t N)
{
*val=U(randsource);
return 1;
}
private:
std::mt19937 randsource;
std::uniform_real_distribution<double> U;
};
int MAIN(int argc,charsys_t* argv[])
{
Herbs::ArrayFixedsize<double> vals(1024*1024*128,GetRand(-4,4));
const size_t N=16;
auto n=N;
while(n)
{
double start=0;
auto ptr=vals.begin();
{
Herbs::TicToc timestamp(start);
while(ptr!=vals.end())
{
pow(2,*ptr);
++ptr;
}
}
// I have set cpu-freq to 1.6 GHz using cpufreq-set
printf("%.15g\t",1.6e9*start/vals.length());
--n;
}
return 0;
}
运行此程序时每次迭代的输出大约为2.25个循环。这似乎非常低,因为pow
的实施似乎是(callgrind
给了我__ieee754_pow
)。
在x86-64上编译GNU / Linux时,程序集中的基准循环看起来像这样:
call _ZN5Herbs6TicTocC1ERd@PLT
movq %r14, %rbx
.p2align 4,,10
.p2align 3
.L28:
vmovsd (%rbx), %xmm1
vucomisd .LC6(%rip), %xmm1
jb .L25
vmovsd .LC7(%rip), %xmm0
call pow@PLT
.L25:
addq $8, %rbx
cmpq %r12, %rbx
jne .L28
movq %rbp, %rdi
call _ZN5Herbs6TicTocD1Ev@PLT
至少调用pow
。我可以信任输出,还是有一些消除事物的黑魔法。
答案 0 :(得分:1)
基准测试功能时,您需要考虑的事项很少。
1)确保缓存未命中不会显着影响结果。在您的情况下,您将遍历大量数据,在这些数据中会出现大量缓存未命中。使用较小的数组,它可以很容易地适应L1缓存并循环几次。
2)确保您正在分析函数调用的副作用,即编译器无法优化这些调用。在你的情况下,编译器没有做好工作,因为pow()
调用没有优化,即使没有副作用。首选使用整数副作用以避免浮点性能出现异常(例如,将浮动浮点数浮动到uint32并添加它们而不是使用浮点数进行添加)。
3)展开你的循环几次,以减少循环的开销。目前,每个循环只执行一次pow,其中循环为这个简单的函数调用增加了相对较大的开销。
4)具有完全优化和内联功能的配置文件。
5)多次运行分析以确保其他过程不会影响您的结果。选择最佳结果进行比较(即来自其他过程的最小干扰量)。