x86 cpu 会在检测到零操作数时提前结束乘法吗?
will x86 cpu perform an early end of multiplication when detects a zero-operand?
================更新=======================
似乎与 Do modern cpus skip multiplications by zero?
重复
但是请允许我保留这个post,因为我在发布之前没有搜索到那个post。并允许我 post 在 答案区 中显示我的测试结果。
==================原文Post=================
以此函数为例:
int mul(int a, int b, int c, int d){
return a*b*c*d;
}
当cpu进入这个函数调用时:
int result = mul(0, 1, 2, 3);
(假设我们不允许编译器做任何优化并且机器码完全按照程序顺序出现。)
我知道现在x86CPU有乱序执行,他发现他得到一个零操作数时会提前执行乘法结束吗?
谢谢!
不,现代 x86-64 CPU 不会因为其中一个为零而停止乘法。乱序执行不会阻止 运行ning 中的任何指令,它只是可能允许一些乱序执行得很好。
使用您编写的代码很容易验证这一点。我们称它为 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)
您可以看到三个单独的乘法,因此它们没有被编译掉或任何类似性质的东西。
@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
.
我检查了assemble代码,它没有优化。
之所以在循环内部使用长乘法表达式,是因为我想减少循环指令占用的时间成本比例。
似乎第一个循环体花费了更多时间(84838us),我认为这是因为 CPU 缓存的预热阶段。我对两个循环体进行了反向定位并得到了类似的结果:前者仍然花费了更多时间。
所以我可以得出和你一样的结论,正如其他朋友所说,x86
上的乘法是时间固定的。
================更新=======================
似乎与 Do modern cpus skip multiplications by zero?
重复但是请允许我保留这个post,因为我在发布之前没有搜索到那个post。并允许我 post 在 答案区 中显示我的测试结果。
==================原文Post=================
以此函数为例:
int mul(int a, int b, int c, int d){
return a*b*c*d;
}
当cpu进入这个函数调用时:
int result = mul(0, 1, 2, 3);
(假设我们不允许编译器做任何优化并且机器码完全按照程序顺序出现。)
我知道现在x86CPU有乱序执行,他发现他得到一个零操作数时会提前执行乘法结束吗?
谢谢!
不,现代 x86-64 CPU 不会因为其中一个为零而停止乘法。乱序执行不会阻止 运行ning 中的任何指令,它只是可能允许一些乱序执行得很好。
使用您编写的代码很容易验证这一点。我们称它为 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)
您可以看到三个单独的乘法,因此它们没有被编译掉或任何类似性质的东西。
@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
.
我检查了assemble代码,它没有优化。
之所以在循环内部使用长乘法表达式,是因为我想减少循环指令占用的时间成本比例。
似乎第一个循环体花费了更多时间(84838us),我认为这是因为 CPU 缓存的预热阶段。我对两个循环体进行了反向定位并得到了类似的结果:前者仍然花费了更多时间。
所以我可以得出和你一样的结论,正如其他朋友所说,x86
上的乘法是时间固定的。