2

从这里:https ://stackoverflow.com/a/2485177/462608

对于共享数据的线程安全访问,我们需要保证
读/写确实发生(编译器不会只是将值存储在寄存器中,而是将更新主内存推迟到很久以后)
不会发生重新排序。假设我们使用 volatile 变量作为标志来指示某些数据是否已准备好被读取。在我们的代码中,我们只是在准备好数据后设置了标志,所以一切看起来都很好。但是如果指令被重新排序以便首先设置标志怎么办?

  • 在哪些情况下,编译器会将值存储在寄存器中并推迟更新主存? [关于上述报价]
  • 上述引文所说的“重新排序”是什么?在什么情况下会发生?
4

2 回答 2

2

问:在哪些情况下编译器会将值存储在寄存器中并延迟更新主存?

答:(这是一个广泛而开放的问题,可能不太适合 stackoverflow 格式。)简短的回答是,只要源语言的语义(每个标签的 C++)允许它并且编译器认为它是有利可图。

问:以上引用所说的“重新订购”是什么?

答:编译器和/或 CPU 发出加载和存储指令的顺序不同于原始程序源的一对一翻译所规定的顺序。

问:在什么情况下会发生?

A:对于编译器,与第一个问题的答案类似,只要原始程序语义允许并且编译器认为它是有利可图的。对于 CPU,情况类似,只要原始(单线程!)结果相同,CPU 就可以根据架构内存模型对内存访问进行重新排序。例如,编译器和 CPU 都可以尝试尽早提升负载,因为负载延迟通常对性能至关重要。

为了强制执行更严格的排序,例如为了实现同步原语,CPU 提供了各种原子和/或栅栏指令,并且编译器可以根据编译器和源语言提供禁止重新排序的方法。

于 2012-05-28T08:29:30.430 回答
0

嗯...在搜索“volatile”关键字时发现了这一点..lol 1. 即使使用缓存,寄存器访问也比内存快得多。例如,如果您有以下内容:

for(i = 0; i < 10000; i++)
{
// whatever...
}

如果变量 i 存储在寄存器中,则循环获得更好的性能。所以一些编译器可能会生成将 i 存储在寄存器中的代码。在循环结束之前,对该变量的更新可能不会发生在内存中。甚至完全有可能 i 永远不会写入内存(例如,i 以后永远不会使用)或溢出到循环体内(例如,内部有一个更重的嵌套循环需要优化,并且没有更多的寄存器)。该技术称为寄存器分配。只要语言标准允许,通常没有优化器规则。它有很多不同的算法。当它发生时很难回答。这就是janneb这么说的原因。如果一个变量没有及时更新,对于多线程代码来说可能真的很糟糕。例如,如果您有这样的代码:

bool objRead = false;
createThread(prepareObj);  // objReady will be turn on in prepareObj thread.
while(!objReady) Sleep(100);
obj->doSomething();

优化器可能生成只测试 objReady 一次的代码(当控制流进入循环时),因为它在循环内部没有改变。这就是为什么我们需要确保读写真的像我们在多线程代码中设计的那样发生。

重新排序比寄存器分配更复杂。编译器和您的 CPU 都可能会更改代码的执行顺序。

void prepareObj()
{
obj = &something;
objReady = true;
}

对于prepareObj函数的观点,不管是先设置objReady还是先设置obj指针都没有关系。由于不同的原因,编译器和 CPU 可能会颠倒两条指令的顺序,例如特定 CPU 流水线的更好并行性、更好的缓存命中数据局部性。您可以阅读 janneb 建议的《计算机体系结构:定量方法》一书。如果我没记错的话,附录 A 是关于重新排序的(如果没有,请转到附录 B 或 C..lol)。

于 2012-08-15T07:50:13.357 回答