Sam*_*zer 3 c c++ optimization performance
我使用以下代码来测试运行时 daxpy 例程初始化后刷新缓存的效果(带有 fill() 和 wall_time() 例程的完整代码位于:http: //codepad.org/QuLT3cbD - 它是 150 行):
#define KB 1024
int main()
{
int cache_size = 32*KB;
double alpha = 42.5;
int operand_size = cache_size/(sizeof(double)*2);
double* X = new double[operand_size];
double* Y = new double[operand_size];
//95% confidence interval
double max_risk = 0.05;
//Interval half width
double w;
int n_iterations = 100;
students_t dist(n_iterations-1);
double T = boost::math::quantile(complement(dist,max_risk/2));
accumulator_set<double, stats<tag::mean,tag::variance> > unflushed_acc;
for(int i = 0; i < n_iterations; ++i)
{
fill(X,operand_size);
fill(Y,operand_size);
double seconds = wall_time();
daxpy(alpha,X,Y,operand_size);
seconds = wall_time() - seconds;
unflushed_acc(seconds);
}
w = T*sqrt(variance(unflushed_acc))/sqrt(count(unflushed_acc));
printf("Without flush: time=%g +/- %g ns\n",mean(unflushed_acc)*1e9,w*1e9);
//Using clflush instruction
//We need to put the operands back in cache
accumulator_set<double, stats<tag::mean,tag::variance> > clflush_acc;
for(int i = 0; i < n_iterations; ++i)
{
fill(X,operand_size);
fill(Y,operand_size);
flush_array(X,operand_size);
flush_array(Y,operand_size);
double seconds = wall_time();
daxpy(alpha,X,Y,operand_size);
seconds = wall_time() - seconds;
clflush_acc(seconds);
}
w = T*sqrt(variance(clflush_acc))/sqrt(count(clflush_acc));
printf("With clflush: time=%g +/- %g ns\n",mean(clflush_acc)*1e9,w*1e9);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
当我运行此代码时,它会报告这些数字的速率和不确定性(置信度为 95%):
无刷新:时间=3103.75 +/- 0.524506 ns 有clflush:时间=4651.72 +/- 201.25 ns
为什么使用 clflush 从缓存中刷新操作数 X 和 Y 会使测量中的噪声增加 100 倍以上?
在 3GHz 下,0.52 ns 是 1.5 个 CPU 周期,201.25 ns 是 604 个 CPU 周期...考虑到当缓存行在缓存层次结构中丢失时,需要几百个 CPU 周期或更多的时间从 DRAM 中读取缓存行,您正在测量引起的方差1 或 2 个高速缓存行未命中...这并不多。在未刷新的情况下,您对平均时间的读取非常严格,并且方差非常严格......没有发生缓存未命中。
我发现,通过在我的 Mac 上将迭代次数增加到 5000 左右(迭代次数为 100 次),我可以获得与未刷新案例一样严格的方差读数。按理说,数据不在缓存中的平均时间比数据全部在缓存中的平均时间要长 - 但它并不像你想象的那么慢 - 这是因为 CPU 在预测数据方面非常有效您的情况是访问模式,并且(推测)在预期使用之前预取数据(实际上,前面有许多缓存行,以隐藏进入 DRAM 的相对较长的延迟)。
CPU 可能会在预期使用之前发出多个数据缓存(和指令缓存)预取……通过同时进行许多内存读取,它可以有效地减少内存延迟(当然,前提是它猜测正确)。这就引入了非决定论。在未刷新的情况下,几乎所有数据都在 1 级数据缓存中 - 堆栈内存引用是在 32KB 数据之外的,因此会溢出 L1 数据缓存,但这并不是很多,很快就会被填充2 级缓存 - 重点是无需访问内存控制器/DRAM。在刷新的情况下,您的数据仅在内存中,因此您会根据处理器预取如何争夺内存控制器(数据和指令)以及共享同一内存控制器的其他内核中发生的情况而获得可变性。通过运行更长时间,您可以让系统进入“正常”模式,以便访问模式和方差下降。