我正在使用以下代码测试在运行时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;
}
当我运行此代码时,它会报告这些数字的速率和不确定性(95%置信水平):
没有刷新:时间= 3103.75 +/- 0.524506 ns 使用clflush:时间= 4651.72 +/- 201.25 ns
为什么用clflush从缓存中刷新操作数X和Y会使测量中的噪声增加超过100倍?
答案 0 :(得分:4)
在3GHz时.52 ns是1.5个CPU周期而201.25 ns是604个CPU周期......考虑到当你在测量的高速缓存层次结构中未命中时从DRAM读取高速缓存行需要几百个CPU周期或更多1或2个缓存行未命中导致的差异......这并不多。在未完成的情况下,你的平均时间读数非常紧张且方差非常紧密......没有发生缓存未命中。
我发现通过在我的Mac上将迭代次数增加到大约5000次(你有100次迭代),我可以像刷新情况一样紧密地读取冲洗情况的方差。理所当然的是,数据不在缓存中的平均时间大于缓存中的全部时间 - 但它并不像您预期的那么慢 - 这是因为CPU在预测中非常有效您的情况是访问模式,并且(推测性地)在预期使用之前预先获取数据(事先许多缓存行,实际上是为了隐藏进入DRAM的相对长的延迟)。
CPU可以在预期使用之前发出几个数据高速缓存(和指令高速缓存)预取...通过同时在飞行中进行许多内存读取,它有效地减少了内存延迟(假设它当然是正确猜测的)。这引入了非决定论。在您未刷新的情况下,几乎所有数据都在1级数据高速缓存中 - 堆栈内存引用是您的32KB数据的补充,因此溢出L1数据高速缓存,但这不是很多,并且很快将从2级缓存 - 重点是没有必要去内存控制器/ DRAM。在刷新的情况下,您的数据仅存储在内存中,因此您可以根据处理器预取如何争用内存控制器(数据和指令)以及共享相同内存控制器的其他内核中发生的任何情况来获得可变性。通过延长运行时间,您可以让系统进入“正常”模式,以便访问模式和方差下降。