创建一个始终返回零的函数,但优化器不知道

时间:2018-07-23 05:11:01

标签: c++ performance optimization compiler-optimization

我想创建一个始终返回零的函数,但是对于优化器来说,这一事实应该并不明显,因此,由于“已知零”状态,使用该值进行的后续计算将不会不断折叠。 / p>

在没有链接时优化的情况下,这通常就像将其放入自己的编译单元一样简单:

int zero() {
  return 0;
}

优化器看不到单位,因此不会发现该函数的始终为零的性质。

但是,我需要与LTO一起使用的东西,以及将来尽可能多的聪明优化方法。我考虑过从全球阅读:

int x;

int zero() {
  return x;
}

...但是在我看来,足够聪明的编译器会注意到x从未被写入,而仍然确定zero()始终为零。

我考虑使用volatile,例如:

int zero() {
  volatile int x = 0;
  return x;
}

...但是可变读取所需副作用的实际语义尚不清楚,并且似乎不能排除函数仍返回零的可能性。

这样的始终为零但不是在编译时的值在几种情况下很有用,例如在两个值之间强制使用no-op依赖性。像这样的东西:a += b & zero()导致a依赖于最终二进制文件中的b,但不会更改a的值。

不要通过告诉我“标准不能保证采取任何方式来做到这一点”来回答这个问题-我很清楚,我正在寻找实用的答案,而不是标准的语言。

4 个答案:

答案 0 :(得分:2)

如果编译器能够解决这个问题,我会感到惊讶:

int not_a_zero_honest_guv()
{
    // static makes sure the initialization code only gets called once
    static int const i = std::ifstream("") ? 1:0;
    return i;
}

int main()
{
    std::cout << not_a_zero_honest_guv();
}

这使用函数本地静态函数的复杂(不可预测)运行时初始化。如果顽皮的小编译器发现空文件名总是会失败,请在其中放置一些非法文件名。

答案 1 :(得分:2)

首先,我认为OP的第三项建议:

int zero() {
  volatile int x = 0;
  return x;
}

实际上可以工作(但这不是我的答案;请参见下文)。 Is it allowed for a compiler to optimize away a local volatile variable?的主题是两周前完全相同的功能,经过很多讨论和不同意见,在此不再赘述。但有关此方面的最新测试,请参见https://godbolt.org/g/SA7k5P


我的答案是在上面添加一个static,即:

int zero() {
  static volatile int x;
  return x;
}

在此处查看一些测试:https://godbolt.org/g/qzWYJt

现在,通过添加static,“可观察到的行为”的抽象概念变得更加可信。通过一点点工作,我可以弄清楚x的地址,尤其是在禁用Address space layout randomization的情况下。这可能在.bss段中。然后,再进行一些工作,我可以将调试器/黑客工具附加到正在运行的进程中,然后更改 x的值。使用volatile,我已经告诉编译器我可以这样做,因此不允许通过优化x来更改这种“可观察的行为”。 (它也许可以通过内联将 call 优化为zero,但我不在乎。)

Is it allowed for a compiler to optimize away a local volatile variable?的标题有点误导,因为讨论的焦点是x stack 上,而不是 local变量。因此,此处不适用。但是我们可以将x从本地范围更改为文件范围,甚至更改为全局范围,如下所示:

volatile int x;
int zero() {
  return x;
}

这不会改变我的观点。


进一步的讨论:

是的,volatile有时会出现问题:例如,请参见https://godbolt.org/g/s6JhpLDoes accessing a declared non-volatile object through a volatile reference/pointer confer volatile rules upon said accesses?中显示的指向易失性指针。

是的,有时(总是?)编译器有错误。

但是我想证明这种解决方案不是一个极端的情况,并且编译器作者之间已经达成共识,我将通过查看现有分析来做到这一点。

John Regehr的2010年博客文章Volatile Structs Are Broken报告了一个错误,该错误在gcc和Clang中都对易失性访问进行了优化。 (它在三个小时内固定。)一位评论员引用了该标准(强调了一点):

  

“ 6.7.3 ...对具有volatile限定类型的对象的访问实现定义的。

Regehr同意,但是补充说,在非边缘案件中如何使用它已经达成共识:

  

是的,定义了对易失性变量的访问权限。但是您错过了以下事实:所有合理的C实现都将对volatile变量的读取视为读取访问,将对volatile变量的写入视为写入访问。

有关更多参考。看到:

这些是有关编译器错误和程序员错误的报告。但是它们显示了volatile应该/如何工作,并且这个答案符合那些准则。

答案 2 :(得分:0)

您会发现每个编译器都具有实现此目的的扩展。

海湾合作委员会:

__attribute__((noinline))
int zero()
{
    return 0;
}

MSVC:

__declspec(noinline)
int zero()
{
    return 0;
}

答案 3 :(得分:0)

在clang和gcc上,破坏变量是可行的,但会带来一些开销

int zero()
{
    int i = 0;
    asm volatile(""::"g"(&i):"memory");
    return i;
}

在gcc的O3下被编译为

    mov     DWORD PTR [rsp-4], 0
    lea     rax, [rsp-4]
    mov     eax, DWORD PTR [rsp-4]
    ret

和叮当声

    mov     dword ptr [rsp - 12], 0
    lea     rax, [rsp - 12]
    mov     qword ptr [rsp - 8], rax
    mov     eax, dword ptr [rsp - 12]
    ret

Live