繁体   English   中英

对于{A = a; B = B; ,“A = a”会在“B = b”之前严格执行吗?

[英]For { A=a; B=b; }, will “A=a” be strictly executed before “B=b”?

假设ABab都是变量, ABab的地址都是不同的。 然后,对于以下代码:

A = a;
B = b;

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

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

编辑:问题的背景如下。 在棋盘游戏AI设计中,对于优化,人们使用无锁共享哈希表 ,如果我们不添加volatile限制,其正确性很大程度上取决于执行顺序。

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

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

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

一个有用的参考

一篇有趣的文章介绍了编译时的内存排序,它说:

内存重新排序的基本规则,编译器开发人员和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产生以下内容( 见实时 ):

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

为什么?

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

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

标准

在C ++标准草案中,这将在1.9 节目执行中介绍( 强调我的未来 ):

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

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

这项规定有时被称为“假设”规则 ,因为只要结果就像是遵守了要求,只要可以从可观察的行为中确定,实施就可以自由地忽视本国际标准的任何要求 。该计划。 例如,实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用,并且没有产生影响程序的可观察行为的副作用。

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

as-if规则,5.1.2.3

关于无锁注意事项的更新

无锁编程简介 ”一文很好地介绍了这个主题,对于无锁共享哈希表实现的OP问题,本节可能是最相关的:

记忆订购

如流程图所示,无论何时对多核(或任何对称多处理器 )进行无锁编程,并且您的环境不保证顺序一致性,您必须考虑如何防止内存重新排序

在今天的体系结构中,强制执行正确内存排序的工具通常分为三类,这会阻止编译器重新排序处理器重新排序

  • 一个轻量级的同步或围栏指令,我将在以后的帖子中讨论 ;
  • 一个完整的记忆围栏指令,我之前已经证明过 ;
  • 提供获取或释放语义的内存操作。

获取语​​义可防止按程序顺序对其后面的操作进行内存重新排序,并且释放语义可防止对其前面的操作进行内存重新排序。 这些语义特别适用于存在生产者/消费者关系的情况,其中一个线程发布一些信息而另一个线程读取它。 我将在以后的文章中再讨论这个问题。

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

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

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

正如您的编辑所示,您正试图将其准确地用于不起作用的地方。

如果你这样做,可能会有兴趣:

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

请注意逗号代替分号。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM