【问题标题】:Does the compiler really optimize to make these two functions the same assembly? [duplicate]编译器是否真的进行了优化以使这两个函数成为同一个程序集? [复制]
【发布时间】:2021-12-05 08:25:43
【问题描述】:

我将它插入 Godbolt 并惊喜地发现这两个函数调用 a()b()-O0 以外的任何其他条件下都是等效的(使用大多数主要编译器):

#include <cmath>

struct A {
    int a,b,c;
    float bar() {
        return sqrt(a + b + c);
    }
};

struct B {
    int a[3];
    float bar() {
        int ret{0};
        for (int i = 0; i<3; ++i) {
            ret += a[i];
        }
        return sqrt(ret);
    }
};

float a() {
    A a{55,67,12};
    return a.bar();
}

float b() {
    B b{55,67,12};
    return b.bar();
}

Godbolt 输出为:

a():
        movss   xmm0, DWORD PTR .LC0[rip]
        ret
b():
        movss   xmm0, DWORD PTR .LC0[rip]
        ret
.LC0:
        .long   1094268577

我不是装配专家,但我想知道这是否真的是真的,他们正在做同样的工作。我什至看不到这个程序集中的哪个位置调用了sqrt,或者long“常量”(?)在那里做什么。

【问题讨论】:

  • 我不明白这个问题。您无需成为装配专家即可看到 a()b() 的装配是相同的。您实际上是在问为什么编译器可以优化这两个函数来做同样的事情吗?
  • 编译器在编译期间计算结果并在程序集中使用计算出的常数。 1094268577 应按位解释为浮点数 (IEEE-754):1094268577 = 0x413936A1 = 11.5758371353。试试自己here
  • 简短回答:这两个函数具有完全相同的可观察行为。
  • 当内联+常量传播可以使输入成为编译时常量时,它不会在运行时执行sqrt;它在编译时评估 sqrt。
  • a+b+c = a+b+c 是的。您没有使用任何全局变量或 volatile 或其他任何东西来防止消除死代码,所以它消除了它。

标签: c++ assembly x86-64 compiler-optimization


【解决方案1】:

这个函数:

float a() {
    A a{55,67,12};
    return a.bar();
}

具有与此完全相同的可观察行为:

float a() {
    return sqrt(55+67+12);
}

b() 也是如此。此外,sqrt(55+67+12) == sqrt(134) == 11.5758369028

Binary representation of the IEEE-754 floating point value 11.5758369028 is 01000001001110010011011010100001。还有那个binary as integer is 1094268577

编译器应用所谓的as if rule 将这两个函数替换为与原始代码具有完全相同的可观察行为的程序集:两个函数都返回一个float,其值为11.5758369028

【讨论】:

  • 啊,谢谢!我没有意识到这会在编译时完成。我已经稍微更改了代码(未在此处发布)以参数化我的函数,现在我看到了一个真正的sqrt 和更多的程序集,但是这两个函数无论如何都是一样的,所以我猜循环展开和其他好东西是仍在发生。看起来很整洁。
  • @johnbakers:是的,具有恒定迭代次数的短循环在源代码中通常很方便,我们不希望编译器因为我们没有手动展开/剥离它们而使 asm 变得更糟。例如Why is a simple loop optimized when the limit is 959 but not 960? 正在推动循环 GCC 尝试完全展开(又名剥离)的极限。这种情况很有趣,因为在完全展开之后,跨迭代的常量传播可以将最终结果减少到一个常量,而不是循环体的 959 个副本。
  • 但是对于更正常的情况,例如使用多个 FP 累加器(或累加器的 SIMD 向量)来隐藏浮点数组总和中的 FP 指令延迟,它可以在 @ 数组上编写短循环987654335@ 而不是手动编写sum0sum1 等,编译器将可靠地优化循环。
猜你喜欢
  • 2017-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-16
  • 2017-08-31
  • 1970-01-01
  • 1970-01-01
  • 2019-12-31
相关资源
最近更新 更多