3

我正在使用以下代码来测试在运行时初始化后刷新缓存的效果 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 倍以上?

4

1 回答 1

4

在 3GHz 时,0.52 ns 是 1.5 个 CPU 周期,201.25 ns 是 604 个 CPU 周期......考虑到当它在缓存层次结构中未命中时从 DRAM 读取缓存线需要几百个 CPU 周期或更多周期,您正在测量引起的方差1 或 2 个缓存行未命中……这并不多。在未刷新的情况下,您对平均时间的读数非常严格,方差非常小……没有发生缓存未命中。

我发现通过在我的 Mac 上将迭代次数增加到大约 5000 次(你有 100 次迭代),我可以像未刷新的情况一样准确地读取已冲洗案例的方差。理所当然地,数据不在缓存中的平均时间比所有数据都在缓存中的时间要长——但它并没有你想象的那么慢——这是因为 CPU 非常有效地预测您的情况是访问模式,并且(推测性地)在您预期的使用之前预取数据(实际上,前面有许多缓存线,以隐藏进入 DRAM 的相对较长的延迟)。

CPU 可能会在预期使用之前发出多个数据缓存(和指令缓存)预取……通过同时进行许多内存读取,它有效地减少了内存延迟(当然前提是它正确猜测)。这引入了非确定性。在您未刷新的情况下,实际上所有数据都在 1 级数据缓存中-堆栈内存引用是您的 32KB 数据的补充,因此会溢出 L1 数据缓存,但这并不多,很快就会从二级缓存——关键是不需要去内存控制器/DRAM。在刷新的情况下,您的数据仅在内存中,因此您会根据处理器预取内存控制器(数据和指令)的竞争方式以及共享同一内存控制器的其他内核中发生的任何事情而获得可变性。

于 2013-04-03T05:23:40.643 回答