为什么改变这些指令的顺序会显著影响性能

Why does changing the order of these instructions significantly affect performance?

本文关键字:影响 性能 顺序 改变 指令 为什么      更新时间:2023-10-16

在学校的作业中,我正在对一个非常大的数字数组执行一个密集运算。在对整个数组的单线程版本进行基准测试并将我的结果与我同学的结果进行比较时,我注意到一些奇怪的行为。

功能如下:

int compute (char a[], int start, int end) {
    int sum = 0;
    int min = a[start];
    int max = a[start];
    for (int i = start; i < end; i++) {
        if (a[i] > max) max = a[i];
        if (a[i] < min) min = a[i];
        int cube = a[i] * a[i] * a[i];
        sum += cube;
    }
    return sum;
}

但是我同学的程序一直运行得更快,通常快得多。他的代码是相同的,除了循环体中指令的顺序:

for (int i = start; i < end; i++) {
    int cube = a[i] * a[i] * a[i];
    sum += cube;
    if (a[i] > max) max = a[i];
    if (a[i] < min) min = a[i];
}

下面是将每个版本的运行时与大小为1,000,000,000的输入数组(用随机带符号字节初始化)进行比较的输出:

Min/max first:
sum = 5445493143089, min = -128, max = 127
Completed in 1.050268 sec
Product-sum first:
sum = 5445493143089, min = -128, max = 127
Completed in 1.010639 sec

我们检查了两个版本的生成程序集,注意到存在相同的指令,只是顺序不同。据我所知,这应该不会产生如此显著的影响,但我可能错了。(我们还注意到所使用的寄存器差异很大,但是这个I 特别是疑问应该会产生影响。)

我们在编译C (-std=c11)和c++ (-std=c++11)时都会遇到这种行为。

为什么这些行的顺序严重影响顺序程序的行为?我们还对该操作的并行版本进行了基准测试,相比之下,它的行为几乎没有变化。我把内存重新排序作为一个可能的罪魁祸首,但这似乎不是问题,因为并行版本实际上没有受到影响(而且分区中没有重叠)。

密集的背对背测试显示行为。Product-sum总是比min/max快,即使在交替和允许缓存的情况下也是如此。

如果我们将显式跳转放入代码中,您可以看到在末尾带有条件的代码在大多数情况下可以避免一次跳转。这与编译器实际生成的代码类似。

第一种形式,最小/最大优先:

    int i = lo;
    goto start;
loop:
    i++;
start:
    if (!(i < hi)) goto end;
    if (!(a[i] > ret.max)) goto label1;
    ret.max = a[i];
label1:
    if (!(a[i] < ret.min)) goto label2;
    ret.min = a[i];
label2:
    long long square = a[i] * a[i];
    ret.sum += square;
    goto loop;
end:

Second form, min/max last:

    int i = lo;
    goto start;
loop:
    i++;
start:
    if (!(i < hi)) goto end;
    long long square = a[i] * a[i];
    ret.sum += square;
    if (!(a[i] > ret.max)) goto label1;
    ret.max = a[i];
label1:
    if (!(a[i] < ret.min)) goto loop;
    ret.min = a[i];
    goto loop;
end:

这可以简单到处理器跳转预测与循环底部的条件跳转效果更好…