为什么生成的相同汇编代码不会导致相同的输出?
Why doesn't the same generated assembler code lead to the same output?
示例代码(t0.c
):
#include <stdio.h>
float f(float a, float b, float c) __attribute__((noinline));
float f(float a, float b, float c)
{
return a * c + b * c;
}
int main(void)
{
void* p = V;
printf("%a\n", f(4476.0f, 20439.0f, 4915.0f));
return 0;
}
调用和执行(通过godbolt.org):
# icc 2021.1.2 on Linux on x86-64
$ icc t0.c -fp-model=fast -O3 -DV=f
0x1.d32322p+26
$ icc t0.c -fp-model=fast -O3 -DV=0
0x1.d32324p+26
生成的汇编代码相同:https://godbolt.org/z/osra5jfYY.
为什么生成的相同汇编代码不会导致相同的输出?
为什么 void* p = f;
很重要?
Godbolt 向您展示了 运行 使用 -S
编译器生成的程序集。但在这种情况下,这不是实际获得 运行 的代码,因为可以在 link 时间完成进一步优化。
尝试改为选中“编译为二进制”框 (https://godbolt.org/z/ETznv9qP4),这将实际编译和 link 二进制文件,然后反汇编它。我们看到在您的 -DV=f
版本中, f
的代码是:
addss xmm0,xmm1
mulss xmm0,xmm2
ret
和以前一样。但是对于 -DV=0
,我们有:
movss xmm0,DWORD PTR [rip+0x2d88]
ret
所以 f
已经转换为一个函数,它只是 return 从内存中加载的常量。在 link 时,编译器能够看到 f
仅使用一组特定的常量参数调用,因此它可以执行过程间常量传播并且 f
仅 return 预先计算的结果。
对 f
的额外引用显然会破坏这一点。可能编译器或 linker 看到 f
的地址已被占用,但没有注意到该地址从未被处理过。所以它假定 f
可能会在程序的其他地方被调用,因此它必须发出可以为任意参数提供正确结果的代码。
至于为什么结果不一样:预计算是严格的,把a*c
和b*c
都求成float
然后相加。所以它的 122457232
结果是 C 规则的“正确”结果,它也是用 -O0
或 -fp-model=strict
编译时得到的结果。 运行时间版本已经优化为(a+b)*c
,这实际上更准确,因为它避免了额外的舍入;它产生 122457224
,更接近 122457225
.
的精确值
示例代码(t0.c
):
#include <stdio.h>
float f(float a, float b, float c) __attribute__((noinline));
float f(float a, float b, float c)
{
return a * c + b * c;
}
int main(void)
{
void* p = V;
printf("%a\n", f(4476.0f, 20439.0f, 4915.0f));
return 0;
}
调用和执行(通过godbolt.org):
# icc 2021.1.2 on Linux on x86-64
$ icc t0.c -fp-model=fast -O3 -DV=f
0x1.d32322p+26
$ icc t0.c -fp-model=fast -O3 -DV=0
0x1.d32324p+26
生成的汇编代码相同:https://godbolt.org/z/osra5jfYY.
为什么生成的相同汇编代码不会导致相同的输出?
为什么 void* p = f;
很重要?
Godbolt 向您展示了 运行 使用 -S
编译器生成的程序集。但在这种情况下,这不是实际获得 运行 的代码,因为可以在 link 时间完成进一步优化。
尝试改为选中“编译为二进制”框 (https://godbolt.org/z/ETznv9qP4),这将实际编译和 link 二进制文件,然后反汇编它。我们看到在您的 -DV=f
版本中, f
的代码是:
addss xmm0,xmm1
mulss xmm0,xmm2
ret
和以前一样。但是对于 -DV=0
,我们有:
movss xmm0,DWORD PTR [rip+0x2d88]
ret
所以 f
已经转换为一个函数,它只是 return 从内存中加载的常量。在 link 时,编译器能够看到 f
仅使用一组特定的常量参数调用,因此它可以执行过程间常量传播并且 f
仅 return 预先计算的结果。
对 f
的额外引用显然会破坏这一点。可能编译器或 linker 看到 f
的地址已被占用,但没有注意到该地址从未被处理过。所以它假定 f
可能会在程序的其他地方被调用,因此它必须发出可以为任意参数提供正确结果的代码。
至于为什么结果不一样:预计算是严格的,把a*c
和b*c
都求成float
然后相加。所以它的 122457232
结果是 C 规则的“正确”结果,它也是用 -O0
或 -fp-model=strict
编译时得到的结果。 运行时间版本已经优化为(a+b)*c
,这实际上更准确,因为它避免了额外的舍入;它产生 122457224
,更接近 122457225
.