【问题标题】:will x86 cpu perform an early end of multiplication when detects a zero-operand? [duplicate]当检测到零操作数时,x86 cpu 会提前结束乘法吗? [复制]
【发布时间】:2017-06-28 12:15:34
【问题描述】:

================更新=======================

似乎与 Do modern cpus skip multiplications by zero? 重复

但是请允许我保留这篇文章,因为我在发布之前没有搜索过那篇文章。并允许我在答案区中发布我的测试结果。

==================原帖=================

以这个函数为例:

int mul(int a, int b, int c, int d){
    return a*b*c*d;
}

当cpu进入这个函数调用时:

int result = mul(0, 1, 2, 3);

(假设我们不允许编译器进行任何优化,并且机器代码完全按照程序顺序显示。)

我知道现在 x86 CPU 有乱序执行,当他发现他得到一个零操作数时,它会提前结束乘法吗?

谢谢!

【问题讨论】:

  • 可能。可能不会。取决于实施。
  • 哪个 CPU?你需要谈一个具体的,否则这个问题是无法回答的。此外,并非所有 CPU 都执行乱序执行(大多数 x86-64 架构会执行,但 RISC 通常不会执行)
  • @UnholySheep 好的,我有更新。
  • 当前所有的 x86 µarch 都有固定时间乘法。

标签: cpu hardware


【解决方案1】:

不,现代 x86-64 CPU 不会仅仅因为其中一个为零而停止乘法运算。乱序执行不会阻止任何指令运行,它只是可能允许一些指令执行得很好,乱序。

这很容易使用您编写的代码进行验证。我们就叫它multiply.c

int mul(int a, int b, int c, int d){
    return a * b * c * d;
}

int main() {
    for (int i = 0; i < 1e8; i++) {
        int result = mul(0, 1, 2, 3);
    }
}

首先验证将“0”替换为“1”时代码运行速度不会变慢:

gcc multiply.c -O0
time ./a.out

real    0m0.387s
user    0m0.378s
sys     0m0.002s

将0改为1后重新运行,实际时间为0.384s,无统计学差异。

接下来看看gcc生成了什么程序集:

gcc -g -c -O0 multiply.c
otool -tvVX multiply.o

...

_mul:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -0x4(%rbp)
    movl    %esi, -0x8(%rbp)
    movl    %edx, -0xc(%rbp)
    movl    %ecx, -0x10(%rbp)
    movl    -0x4(%rbp), %ecx
    imull   -0x8(%rbp), %ecx
    imull   -0xc(%rbp), %ecx
    imull   -0x10(%rbp), %ecx
    movl    %ecx, %eax
    popq    %rbp
    retq
    nopw    %cs:_mul(%rax,%rax)

您可以看到三个单独的乘法,因此它们不会被编译掉或任何类似性质的东西。

【讨论】:

    【解决方案2】:

    @Douglas B. Staple

    感谢您的基准测试。我也做过类似的测试。

    #include<stdio.h>
    #include"../base/benchmark.h"
    
    #define MAX (100 * 10000)
    int product0;
    int product1;
    int zero = 0;
    int one = 1;
    int main(void){
        TSTAMP_INIT();
        long time0, time2;
    
                                                                TSTAMP();
        for(int i = 0; i < MAX; i++){
            product0 *=  zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero * zero ;
        }
                                                                TSTAMP(&time0);
        for(int i = 0; i < MAX; i++){
            product1 =  one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one * one ;
    
        }
    
                                                                TSTAMP(&time2);
        printf("%ld %ld\n", time0, time2);
        return 0;
    }
    

    编译并运行:

    $ gcc -o t t.c ../base/benchmark.c -O0
    $ ./t
    84828 74827
    

    时间单位为us

    我检查了汇编代码,它没有优化。

    我在循环中使用长乘法表达式的原因是,我想减少循环指令占用的时间成本比例。

    似乎第一个循环体花费了更多时间(84838us),我认为这是因为 CPU 缓存的预热阶段。我有两个循环体的反向定位并得到类似的结果:仍然前者花费更多时间。

    所以我可以得出和你一样的结论,正如其他朋友所说,x86 上的乘法是固定时间的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-06
      • 1970-01-01
      • 1970-01-01
      • 2017-06-16
      • 2019-01-05
      相关资源
      最近更新 更多