为什么生成的相同汇编程序代码不会导致相同的输出?

Why doesn't the same generated assembler code lead to the same output?

提问人:pmor 提问时间:11/27/2021 最后编辑:pmor 更新时间:11/29/2021 访问量:116

问:

示例代码 ():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;

C x86-64 浮点精度 ICC

评论

0赞 Marco Bonelli 11/27/2021
什么在迟到...您确定可执行文件是相同的吗?说什么?diff
0赞 pmor 11/27/2021
我还没有比较可执行文件。据我了解,godbolt.org(还)不允许下载(或在线比较)可执行文件。
0赞 0___________ 11/27/2021
使用调试器 - 单步执行汇编代码。查看寄存器调用之前真正包含的内容,具体取决于您在此处未看到的其他初始化代码。因此,快速数学通常会给出“有趣”的结果。printf
1赞 Nate Eldredge 11/27/2021
是的,我想知道显示的程序集是否真的与正在执行的代码匹配。例如,也许正在发生链接时间优化?
5赞 Nate Eldredge 11/27/2021
啊,当你选择“编译为二进制”时,请查看。该版本已简化为仅返回一个常量 - 大概是过程间常量传播,一旦链接器可以看到没有其他调用。取地址可能会愚弄它。-DV=0fff

答:

5赞 Nate Eldredge 11/27/2021 #1

Godbolt 向你展示了通过使用 运行编译器发出的程序集。但在这种情况下,这不是实际运行的代码,因为可以在链接时进行进一步的优化。-S

尝试选中“编译为二进制文件”框(https://godbolt.org/z/ETznv9qP4),这将实际编译和链接二进制文件,然后对其进行反汇编。我们看到,在您的版本中,代码为:-DV=ff

 addss  xmm0,xmm1
 mulss  xmm0,xmm2
 ret 

和以前一样。但是,我们有:-DV=0

 movss  xmm0,DWORD PTR [rip+0x2d88]
 ret

所以已经转换为一个函数,该函数仅返回从内存加载的常量。在链接时,编译器能够看到它只被一组特定的常量参数调用,因此它可以执行过程间常量传播,并且只返回预先计算的结果。fff

有一个额外的参考显然违背了这一点。编译器或链接器可能看到其地址已被占用,并且没有注意到该地址从未执行任何操作。因此,它假设可以在程序的其他地方调用,因此它必须发出代码,以便为任意参数提供正确的结果。fff

至于为什么结果不同:预计算是严格进行的,评估和作为,然后将它们相加。所以它的结果是 C 规则下的“正确”结果,也是你用 或 编译时得到的。运行时版本已优化为 ,这实际上更准确,因为它避免了额外的舍入;它产生 ,它更接近 的确切值。a*cb*cfloat122457232-O0-fp-model=strict(a+b)*c122457224122457225

评论

0赞 Peter Cordes 11/27/2021
恒定传播可能已经完成,也许引入了两个单独的舍入步骤?不,这不能解释它;作为双倍,这些操作都是精确的,因为数字不是太大。double
2赞 Nate Eldredge 11/27/2021
@PeterCordes:整理好了。常量传播版本的计算非常严格,在执行所有操作时会产生所有舍入错误。运行时版本优化为更快、更准确,但 C 评估规则并不严格正确。默认情况下,看起来 icc 有效地做到了。(a*c)+(b*c)float(a+b)*c-ffast-math
0赞 Peter Cordes 11/27/2021
是的,它确实在某种程度上(默认是),尽管问题中使用的 Godbolt 链接(即 fast=2)使其与 GCC 一样具有侵略性。intel.com/content/www/us/en/develop/documentation/....我不清楚默认情况下允许/不允许的确切内容,但 FP 关联数学假设是,因此它可以自动矢量化并在人们对其进行基准测试时看起来不错。(和分布式)-fp-model fast=1-fp-model=fast-ffast-mathfast=1
2赞 Nate Eldredge 11/27/2021
@PeterCordes:啊,好吧,让我们回到严格版本。(有趣的是,在我检查文档之前,我的第一个猜测是做,ICEs:godbolt.org/z/YW96vYKj9-fp-model=strict-fp-model fast=0)
1赞 Nate Eldredge 11/29/2021
@pmor: 嗯......在细则中,ICC手册确实说-fp-model=precision=strict暗示)是“严格的ANSI一致性”所必需的。因此,没有这个选项就不能声称自己是符合要求的实现,因此没有人可以阻止他们定义任何他们喜欢的宏。icc