【问题标题】:Why doesn't the same generated assembler code lead to the same output?为什么生成的相同汇编代码不会导致相同的输出?
【发布时间】:2022-01-04 21:44:11
【问题描述】:

示例代码 (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; 很重要?

【问题讨论】:

  • 什么在tarnation...你确定可执行文件是相同的吗? diff 说什么?
  • 我还没有比较可执行文件。据我了解,godbolt.org(还)不允许下载(或在线比较)可执行文件。
  • 使用调试器 - 跳过汇编代码。查看在调用printf 之前真正 包含哪些寄存器,具体取决于您在此处看不到的其他初始化代码。因此,快速数学通常会给出“有趣”的结果。
  • 是的,我想知道显示的程序集是否实际上与正在执行的代码匹配。例如,也许正在发生链接时间优化?
  • 啊,当你选择“编译为二进制”时检查一下。 -DV=0 版本已将 f 减少为仅返回一个常量 - 大概是过程间常量传播,一旦链接器可以看到没有其他对 f 的调用就完成了。取f 的地址可能会被愚弄。

标签: c floating-point x86-64 floating-accuracy icc


【解决方案1】:

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

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

 addss  xmm0,xmm1
 mulss  xmm0,xmm2
 ret 

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

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

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

额外引用f 显然会破坏这一点。可能编译器或链接器看到f 的地址已被占用,并且没有注意到该地址没有做任何事情。所以它假设f 可能在程序的其他地方被调用,因此它必须发出代码,为任意参数提供正确的结果。

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

【讨论】:

  • 可能已经使用double 完成了持续传播,可能会引入两个单独的舍入步骤?不,这无法解释;因为数字不是太大,所以这些操作都是精确的。
  • @PeterCordes:整理出来了。常量传播版本严格评估(a*c)+(b*c),在执行所有操作时会产生所有舍入错误float。运行时版本优化为 (a+b)*c,它更快、更准确,但 C 评估规则并不严格正确。看起来 icc 默认有效地执行-ffast-math
  • 是的,它确实有点(默认为-fp-model fast=1),尽管问题中的Godbolt链接使用-fp-model=fast(即fast=2)使其与GCC-ffast-math一样具有攻击性。 intel.com/content/www/us/en/develop/documentation/…。我不清楚默认 fast=1 到底允许/不允许什么,但是 FP 关联数学假设是这样的,因此它可以自动矢量化并且在人们对其进行基准测试时看起来不错。 (和分配)
  • @PeterCordes:啊,好吧,-fp-model=strict 让我们回到严格的版本。 (有趣的是,在我查看文档之前,我的第一个猜测是 -fp-model fast=0 和 ICE:godbolt.org/z/YW96vYKj9
  • @pmor: Wellllll... 在细则中,ICC 手册确实说-fp-model=precise (which =strict implies) is required for "strict ANSI conformance"。因此,icc without 这个选项并不声称是一个符合要求的实现,因此没有人可以阻止他们定义他们喜欢的任何宏。
猜你喜欢
  • 1970-01-01
  • 2022-12-05
  • 1970-01-01
  • 2021-09-16
  • 2019-10-07
  • 1970-01-01
  • 2021-03-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多