对于{A = a; B = B; },将" A = a"在" B = b"之前严格执行?

时间:2014-09-15 11:46:06

标签: c++ c optimization compiler-construction standards

假设ABab都是变量,地址AB,{{ 1}}和a都不同。然后,对于以下代码:

b

C和C ++标准是否明确要求在A = a; B = b; 之前严格执行A=a?鉴于B=bABa的地址都不同,编译器是否允许交换两个语句的执行顺序以用于某些目的,例如优化?

如果我的问题的答案在C和C ++中有所不同,我想知道两者。

编辑:问题的背景如下。在棋盘游戏AI设计中,对于优化,人们使用lock-less shared-hash table,如果我们不添加b限制,则其正确性在很大程度上取决于执行顺序。

6 个答案:

答案 0 :(得分:55)

这两个标准都允许这些指令不按顺序执行,只要这不会改变可观察的行为。这被称为as-if规则:

请注意,正如评论中所指出的那样,"可观察行为"是具有已定义行为的程序的可观察行为。如果你的程序有未定义的行为,那么编译器就可以免于推理。

答案 1 :(得分:25)

编译器只有义务模拟程序的可观察行为,因此如果重新排序不违反该原则,那么它将被允许。假设行为定义良好,如果您的程序包含undefined behavior,例如数据竞争,那么程序的行为将是不可预测的,并且评论将需要使用某种形式的同步来保护关键部分。

有用的参考资料

一篇有趣的文章是Memory Ordering at Compile Time,它说:

  

记忆重新排序的基本规则,这是普遍遵循的   编译器开发人员和CPU供应商可以表达如下:

     
    

您不得修改单线程程序的行为。

  

示例

本文提供了一个简单的程序,我们可以看到这个重新排序:

int A, B;  // Note: static storage duration so initialized to zero

void foo()
{
    A = B + 1;
    B = 0;
}

并显示更高的优化级别B = 0A = B + 1之前完成,我们可以使用godbolt重现此结果,使用-O3生成以下内容( see it live 的):

movl    $0, B(%rip) #, B
addl    $1, %eax    #, D.1624

为什么?

为什么编译器重新排序?文章解释了这与处理器完全相同的原因,因为架构的复杂性:

  

正如我在开始时提到的,编译器会修改内存的顺序   交互出于与处理器相同的原因 -   性能优化。这种优化是直接后果   现代CPU复杂性。

标准

在C ++标准草案中,1.9 程序执行部分对此进行了介绍,其中说明了(强调我的前进):

  

本国际标准中的语义描述定义了一个   参数化非确定性抽象机。这个国际   标准对符合结构没有要求   实现。特别是,他们不需要复制或模仿   抽象机器的结构。 相反,符合实施   需要模仿(仅)抽象的可观察行为   机器,如下所述。 5

脚注5告诉我们这也称为 as-if规则

  

此规定有时称为“as-if”规则,因为   实施可以忽略任何要求   国际标准只要结果就好像要求一样   从可观察的角度来看,已经服从了   行为的程序。例如,实际的实施需要   如果它可以推导出它的值,则不评估表达式的一部分   未使用,没有影响可观察行为的副作用   制作该节目。

草案C99和草案C11标准在5.1.2.3 程序执行部分中介绍了这一点,尽管我们必须转到索引才能看到它被称为 as-if规则在C标准中:

  

as-if rule,5.1.2.3

无锁注意事项的更新

文章An Introduction to Lock-Free Programming很好地涵盖了这个主题,对于OP关于无锁共享哈希表实现的关注,本节可能是最相关的:

  

内存订购

     

如流程图所示,无论何时进行无锁编程   多核(或任何symmetric multiprocessor),以及您的环境   不保证顺序一致性,你必须考虑如何防止   memory reordering

     

在今天的架构中,强制执行正确内存排序的工具   通常分为三类,这会阻止compiler reorderingprocessor reordering

     
      
  • 轻量级同步或围栏指令,我将在future posts;
  • 中讨论   
  • 完整的记忆围栏指令,我demonstrated previously;
  •   
  • 提供获取或释放语义的内存操作。
  •   
     

获取语义可防止对后续操作进行内存重新排序   它按程序顺序排列,释放语义可防止内存重新排序   之前的操作。这些语义特别适合   在有生产者/消费者关系的情况下,其中一个   线程发布一些信息,另一个读取它。我也会   在未来的帖子中更多地谈论这个。

答案 2 :(得分:3)

如果没有指令的依赖性,如果最终结果不受影响,这些也可能无序执行。您可以在调试以更高优化级别编译的代码时观察到这一点。

答案 3 :(得分:1)

由于A = a;和B = b;在数据依赖性方面是独立的,这应该不重要。如果前一条指令的输出/结果影响后续指令的输入,则排序很重要,否则不重要。这通常是严格顺序执行。

答案 4 :(得分:1)

我的阅读是,这需要通过C ++标准工作;但是,如果您尝试将其用于多线程控制,则它在该上下文中不起作用,因为这里没有任何内容可以保证寄存器以正确的顺序写入内存。

正如您的编辑所示,您正试图将其用于无法正常工作的位置。

答案 5 :(得分:0)

如果你这样做可能会引起人们的兴趣:

{ A=a, B=b; /*etc*/ }

请注意逗号代替分号。

然后C ++规范和任何确认编译器必须保证执行顺序,因为总是从左到右计算逗号运算符的操作数。 这确实可以用来防止优化器通过重新排序来破坏你的线程同步。逗号实际上成为一个障碍,不允许重新排序。