ACc*_*tor 51 c c++ compiler-construction optimization standards
假设A,B,a,和b都是变量,的地址A,B,a,和b都是不同的.然后,对于以下代码:
A = a;
B = b;
Run Code Online (Sandbox Code Playgroud)
C和C++标准是否明确要求A=a在之前严格执行B=b?考虑到的地址A,B,a,和b都不同,编译器允许交换两个语句的执行顺序为某种目的,如优化?
如果我的问题的答案在C和C++中有所不同,我想知道两者.
编辑:问题的背景如下.在棋盘游戏AI设计中,对于优化,人们使用无锁共享哈希表,如果我们不添加volatile限制,其正确性很大程度上取决于执行顺序.
Dav*_*nan 56
这两个标准都允许这些指令不按顺序执行,只要这不会改变可观察的行为.这被称为as-if规则:
请注意,正如评论中指出的那样,"可观察行为"的含义是具有已定义行为的程序的可观察行为.如果您的程序有未定义的行为,那么编译器就可以免于推理.
Sha*_*our 25
编译器只有义务模拟程序的可观察行为,因此如果重新排序不违反该原则,那么它将被允许.假设行为定义良好,如果您的程序包含未定义的行为(如数据竞争),则程序的行为将是不可预测的,并且评论将需要使用某种形式的同步来保护关键部分.
一个有用的参考
一篇有趣的文章介绍了编译时的内存排序,它说:
内存重新排序的基本规则,编译器开发人员和CPU供应商普遍遵循,可以表达如下:
你不应该修改单线程程序的行为.
一个例子
本文提供了一个简单的程序,我们可以看到这个重新排序:
int A, B; // Note: static storage duration so initialized to zero
void foo()
{
A = B + 1;
B = 0;
}
Run Code Online (Sandbox Code Playgroud)
并且在B = 0之前完成了更高优化级别的显示A = B + 1,并且我们可以使用godbolt重现此结果,使用时它会-O3产生以下内容(请参见实时):
movl $0, B(%rip) #, B
addl $1, %eax #, D.1624
Run Code Online (Sandbox Code Playgroud)
为什么?
为什么编译器重新排序?文章解释了这与处理器完全相同的原因,因为架构的复杂性:
正如我在开始时提到的,编译器修改了内存交互的顺序,原因与处理器的相同 - 性能优化.这种优化是现代CPU复杂性的直接结果.
标准
在C++标准草案中,这将在1.9 程序执行部分中介绍(强调我的未来):
本国际标准中的语义描述定义了参数化的非确定性抽象机器.本国际标准对符合实施的结构没有要求.特别是,它们不需要复制或模拟抽象机器的结构.相反,需要符合实现来模拟(仅)抽象机器的可观察行为,如下所述.五
脚注5告诉我们这也被称为as-if规则:
这项规定有时被称为"假设"规则,因为只要结果就像是遵守了要求,只要可以从可观察的行为中确定,实施就可以自由地忽视本国际标准的任何要求.该计划.例如,实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用,并且没有产生影响程序的可观察行为的副作用.
C99草案和C11标准草案在5.1.2.3 程序执行部分涵盖了这一点,尽管我们必须转到索引,看它在C标准中也被称为as-if规则:
as-if规则,5.1.2.3
关于无锁注意事项的更新
" 无锁编程简介 "一文很好地介绍了这个主题,对于无锁共享哈希表实现的OP问题,本节可能是最相关的:
记忆订购
如流程图所示,无论何时对多核(或任何对称多处理器)进行无锁编程,并且您的环境不保证顺序一致性,您必须考虑如何防止 内存重新排序.
在今天的体系结构中,强制执行正确内存排序的工具通常分为三类,这会阻止编译器重新排序和处理器重新排序:
获取语义可防止按程序顺序对其后面的操作进行内存重新排序,并且释放语义可防止对其前面的操作进行内存重新排序.这些语义特别适用于存在生产者/消费者关系的情况,其中一个线程发布一些信息而另一个线程读取它.我将在以后的文章中再讨论这个问题.