是否允许C++优化器在函数调用中移动语句?

Mar*_* Ba 14 c++ optimization inline operator-precedence

注意:这里根本没有多线程.刚刚优化的单线程代码.

函数调用引入了序列点.(显然).

它是否遵循一个编译器(如果优化内联函数)是能够移动/交融的任何指示前/后函数的说明?(只要它能"证明"没有明显的可观察效果.)


解释性背景:

现在,有一篇很好的文章.作为C++的基准测试类,作者说:

我们时间的代码不会被优化器重新排列,并且总是位于对now()的开始/结束调用之间,因此我们可以保证我们的时序有效.

我问他怎么可以肯定,尼克回答说:

您可以在此答案中查看评论 https://codereview.stackexchange.com/a/48884.我引用:"由于允许编译器进行优化,我会小心计算不是函数的函数.我不确定排序要求和对此类程序的可观察行为理解.通过函数调用,不允许编译器在调用点之间移动语句(它们在调用之前或之后排序)."

我们所做的基本上是抽象的可调用(函数,lambda,由lambda包围的代码块),并callable(factor)measure结构内部有一个signle调用 作为屏障(不是多线程中的障碍,我相信我传达了这个消息).

我对此非常不确定,特别是引用:

通过函数调用,不允许编译器在调用点之间移动语句(它们在调用之前或之后排序).

现在,我总是认为当优化器内联某些函数时(在(简单)基准测试场景中可能就是这种情况),只要它不影响可观察行为,就可以自由地重新排列任何它喜欢的东西.

也就是说,就语言/优化器而言,这两个片段完全相同:

void f() {
  // do stuff / Multiple statements
}

auto start = ...;
f();
auto stop = ...;
Run Code Online (Sandbox Code Playgroud)

auto start = ...;
  // do stuff / Multiple statements
auto stop = ...;
Run Code Online (Sandbox Code Playgroud)

Pup*_*ppy 9

现在,我总是认为当优化器内联某些函数时(在(简单)基准测试场景中可能就是这种情况),只要它不影响可观察行为,就可以自由地重新排列任何它喜欢的东西.

绝对是.优化器甚至不需要为理论上的内容进行内联.

但是,时序功能是可观察的行为 - 特别是它们是系统的I/O. 如果以与其他I/O调用不同的顺序执行,优化器无法知道I/O将产生相同的结果(显然不会),这可能包括非显而易见的事情,例如甚至可以调用系统调用的内存分配调用得到他们的记忆.

这基本上意味着总的来说,对于大多数函数调用,优化器不能进行大量的重新安排,因为它可能涉及大量的状态而无法推理.

此外,优化器无法真正知道重新安排函数调用实际上会使代码运行得更快,并且会使调试变得更加困难,因此他们没有很大的动力去调整程序的订单.

基本上,从理论上讲,优化器可以做到这一点,但实际上它不会这样做,因为这样做将是一项巨大的任务,不会带来很多好处.

如果您的基准测试非常简单,或者几乎完全由原始操作(如整数加法)组成,那么您只会遇到这样的条件 - 在这种情况下,您还是要检查装配.

  • @MartinBa:你可能想要获得那个.优化者可以通过许多方式搞乱基准,而不是问题中理论化的方式.它并没有真正"搞砸".优化器只是指出您的基准不具代表性.无论如何,如果你能够找到这个例子,那么阅读这个问题(和我)的每个人都会受益. (3认同)
  • @Puppy好点!确实应该将计时器视为IO功能.然而,单凭这一点并不能确保使用体内指令进行排序,这些指令没有任何副作用,并且在I/O之前没有与之关联的顺序. (3认同)
  • 时间是不可观察的,因为标准没有规定执行任何事情需要多长时间.此外,时钟可以任意向前和向后移动.因此,您永远无法证明此优化已发生.这使它合法化. (2认同)

cma*_*ter 5

您的关注是完全有效的,如果优化器可以证明这不会改变可观察的行为(除运行时之外),则允许优化器通过函数调用移动任何东西.

关于使用函数来阻止优化器执行操作的要点不是告诉优化器该函数.也就是说,函数不能内联,并且不能包含在同一个编译单元中.由于优化器通常是编译器功能,因此将函数定义移动到不同的编译单元会剥夺优化器所需的信息以证明函数的任何信息,从而阻止它在函数调用中移动任何内容.

请注意,这假设没有链接器进行全局分析以进行优化.如果确实如此,它仍然可以吸引你.


Ben*_*igt 5

你引用的评论没有考虑的是序列点主要不是关于执行的顺序(虽然它们确实约束它,它们不是完全障碍),而是关于表达式的值.

C++ 11实际上完全摆脱了"序列点"术语,而是讨论了"值计算"和"副作用"的排序.

为了说明,以下代码表现出未定义的行为,因为它不遵守排序:

int a = 5;
int x = a++ + a;
Run Code Online (Sandbox Code Playgroud)

这个版本定义明确:

int a = 5;
a++;
int x = a + a;
Run Code Online (Sandbox Code Playgroud)

当副作用和值的计算顺序点/排序保证我们的,就是a用在x = a + aIS 6,没有5.所以编译器不能将其重写为:

int a = 5;
int x = a + a;
a++;
Run Code Online (Sandbox Code Playgroud)

但是,将其重写为以下内容是完全合法的:

int a = 5;
int x = (a+1) + (a+1);
a++;
Run Code Online (Sandbox Code Playgroud)

分配x和分配之间的执行顺序a不受约束,因为它们都不是volatile或者atomic<T>它们不是外部可见的副作用.