在没有优化的情况下添加冗余分配可以加快代码的速度

我发现一个有趣的现象:


#include<stdio.h>

#include<time.h>


int main() {

    int p, q;

    clock_t s,e;

    s=clock();

    for(int i = 1; i < 1000; i++){

        for(int j = 1; j < 1000; j++){

            for(int k = 1; k < 1000; k++){

                p = i + j * k;

                q = p;  //Removing this line can increase running time.

            }

        }

    }

    e = clock();

    double t = (double)(e - s) / CLOCKS_PER_SEC;

    printf("%lf\n", t);

    return 0;

}

我在i5-5257U Mac OS上使用GCC 7.3.0编译代码,没有进行任何优化。这是平均运行时间超过10倍: 还有其他人在其他Intel平台上测试该案例并获得相同的结果。 我将在这里发布由GCC生成的程序集。两种汇编代码之间的唯一区别是,在较快的一种汇编代码之前,还有两项操作:在此处输入图片说明

addl $1, -12(%rbp)


movl    -44(%rbp), %eax

movl    %eax, -48(%rbp)

那么,为什么用这样的分配程序运行得更快?


彼得的回答很有帮助。在AMD Phenom II X4 810和ARMv7处理器(BCM2835)上进行的测试显示了相反的结果,该结果支持存储转发加速特定于某些Intel CPU。

而BeeOnRope的评论和建议催着我重写的问题。:)

这个问题的核心是与处理器架构和组装相关的有趣现象。因此,我认为值得讨论。


浮云间
浏览 442回答 3
3回答

慕娘9325324

请注意,call/ret不会创建循环承载的依赖项,因为由推送的地址call来自推测执行+分支预测。当存储不依赖于数据时,多次存储/重载到同一地址可以使每个时钟维持一个时钟。执行ret指令可以每个时钟执行一次,比call指令落后5个周期。(当然,调用/ ret都是分支,因此它们彼此竞争执行资源,因此甚至没有瓶颈。)可能是问题,是a&nbsp;push/pop rbp或x=foo(x)by ref。

BIG阳

我认为这个问题没有用。您开始时的一大不利之处是没有优化就进行编译,这对于“为什么Y的性能表现得像Z”已经是一个巨大的危险信号-但由于编译器仅针对较慢的情况发出了额外的指令,因此事实证明这很有趣在组装级别的问题。即,您几乎可以删除问题的C起源,以及您在不进行优化的情况下进行编译的事实,并询问程序集的行为,并且可能避免雪崩式的雪崩
打开App,查看更多内容
随时随地看视频慕课网APP