我已经读过一些CPU重新排序指令,但这对于单线程程序来说不是问题(指令仍会在单线程程序中重新排序,但看起来好像指令是按顺序执行的),这只是一个问题用于多线程程序.
为了解决指令重新排序的问题,我们可以在代码中的适当位置插入内存屏障.
但x86 CPU是否重新排序指令?如果没有,那么就没有必要使用内存屏障了吧?
x86 assembly multithreading cpu-architecture memory-barriers
ARM允许重新排序加载后续存储,以便以下伪代码:
// CPU 0 | // CPU 1
temp0 = x; | temp1 = y;
y = 1; | x = 1;
可以导致temp0 == temp1 == 1(并且,这在实践中也是可观察到的).我无法理解这是怎么发生的; 似乎有序提交会阻止它(这是我的理解,它存在于几乎所有的OOO处理器中).我的理由是"在提交之前,负载必须具有其值,它在存储之前提交,并且在提交之前,存储的值不会对其他处理器可见."
我猜我的一个假设肯定是错的,并且必须遵循下列之一:
说明不需要提交一路有序.稍后的存储可以安全地提交并在之前的加载之前变得可见,只要在存储提交核心时可以保证先前的加载(以及所有中间指令)不会触发异常,并且加载的地址是保证与商店不同.
负载可以在其值已知之前提交.我不知道如何实现这一点.
商店在提交之前可以显示.也许某个内存缓冲区允许将存储转发到另一个线程的加载,即使负载先前已加入?
还有别的吗?
有许多假设的微体系结构特征可以解释这种行为,但我最好的是那些实际存在于现代弱有序CPU中的那些.
我试图理解英特尔系统编程指南的 8.2节(PDF格式的第3卷).
特别是,我看到两种不同的重新排序方案:
8.2.3.4载荷可以与较早的商店重新排序到不同的地点
和
8.2.3.5允许处理器内转发
但是,我不明白这些场景与可观察效果POW之间的区别.这些部分提供的示例似乎可以互换.8.2.3.4例子可以通过8.2.3.5规则以及它自己的规则来解释.反之亦然,对我来说也是如此,尽管在那种情况下我并不确定.
所以这是我的问题:有没有更好的例子或解释8.2.3.4的可观察效果与8.2.3.5的可观察效果有何不同?
由于存储负载转发,某些负载指令能否在全局范围内不可见?换句话说,如果加载指令从存储缓冲区中获取其值,则它永远不必从高速缓存中读取。
通常说来,当从L1D缓存读取负载时,该负载在全局范围内可见,因此,未从L1D读取的负载应使其在全局上不可见。
在我的理解中,CPU为了优化而改变了写在机器代码上的操作顺序,这被称为乱序执行。
在术语“内存顺序”中,它定义了访问内存的顺序。例如,在宽松的顺序中,它定义了非常弱的排序规则,并且很容易发生执行重排序。
x86 中有一些内存排序模型,例如 TSO。在这样的存储器排序模型中,定义了处理器的存储器访问顺序的语义。
我不明白的是他们之间的关系。内存顺序是一种乱序执行吗?OoOe还有其他方法吗?
或者说,内存顺序是乱序执行的实现,处理器的所有重新排序都是基于语义的吗?
我对指令流水线有一些疑问。
我有一个大会
0x111: div %ecx
0x112: add $0x13,%ecx #The address 112 is not real, I just kept to show that there is no instructions between div and add
Run Code Online (Sandbox Code Playgroud)
程序计数器,RIP 指向0x111. 我的疑问是,将处理器执行(指令流水线或顺序变化/不限原因)0x112's指令而RIP处于0x111。如果我在 RIP 停止执行0x111,是否有任何机会0x112被执行并且%ecx价值是0X13?
这是一个后续问题
有很多文章和博客提到 Java 和 JVM 指令重新排序,这可能会导致用户操作中出现反直觉的结果。
当我要求演示 Java 指令重新排序导致意外结果时,有几条评论说,更普遍的关注领域是内存重新排序,并且很难在 x86 CPU 上进行演示。
指令重新排序只是内存重新排序、编译器优化和内存模型等更大问题的一部分吗?这些问题真的是 Java 编译器和 JVM 特有的吗?它们是否特定于某些 CPU 类型?
java cpu-architecture memory-barriers instruction-reordering