x86 CPU上围栏内存存储的优化

6
mov 0x0ff, 10
sfence 
mov 0x0ff, 12
sfence

它能在x86-CPU上执行吗:

 mov 0x0ff, 12
 sfence

?


1
两个 sfence 指令是多余的(相关:Does SFENCE prevent the Store Buffer hiding changes from MESI?)。但即使没有它们,我认为另一个线程有时也可能观察到 10。不过,在存储器队列提交到 L1D 之前,存在一些合并的证据,尽管我找不到关于此的 SO 答案或评论。 - Peter Cordes
1
发现了这个问题:Intel Skylake上存储循环的性能异常差且双峰奇怪,有证据表明,相邻的存储到同一缓存行的操作会在存储缓冲区中合并,并作为一个更新提交。如果这种情况发生在对同一位置的存储操作上,则10可能永远不会提交到L1d。我不知道sfence是否可以防止这种情况,我认为在理论上它不必这样做,但它可能会阻止实际CPU上的合并。 - Peter Cordes
2
@PeterCordes sfence会清空存储缓冲区(根据英特尔的说法),因此它应该可以防止合并。它对于排序来说是多余的,但我认为对于可见性来说不是这样。 - Margaret Bloom
2
@eugene - sfence不是一个无操作指令,因为它会对非暂时性存储器进行栅栏操作,这些存储器在彼此之间或常规存储器之间通常没有顺序。 - BeeOnRope
1
@margaret - 我也阅读了英特尔文档,其中列出了 sfence 作为排空存储缓冲区,但我觉得很难相信(至少在同步排空存储缓冲区之前),因为它似乎意味着 sfence; lfence 将等同于 mfence,但英特尔明确表示它不是,并且执行速度更快,因此从实际角度来看,我认为它不是等效的。我觉得这种语言是早期留下的,不能依赖它,尽管我承认我对此不太清楚。 - BeeOnRope
显示剩余6条评论
1个回答

3

是的,有些CPU可以按照您的建议执行。

即使您在其中加入了更强的围栏,例如mfence或使用锁定指令,也不能保证第一次写入不会被优化掉。

总的来说,这是真实的:排序和围栏规则基本上告诉您哪些执行是不允许的,因此保证永远不会出现,但是考虑到允许发生的补充执行集合,则通常没有保证任何特定的执行可能实际上被观察到。

尽管如此,在当前的x86芯片上,我非常确定您始终能够观察到偶尔出现的10值(即使完全省略了围栏),因为您可以偶尔在两个存储之间获得中断,从而允许您读取10。

然而,这并不是保证-人们当然可以想象像Denver或Transmeta这样的动态优化x86架构可以压缩上述序列,删除两个围栏和第一个存储,使20成为唯一可观察的值。


2
@Gilgamesz - mfence不会导致上下文切换,我不认为我暗示过这一点。我的意思是即使使用“更重”的fence,也不能保证在所有过去、现在和未来的x86芯片上都观察到10。当然,如果mfence需要很多周期,可能在mfence指令附近发生上下文切换的可能性更大,但我的回答并不依赖于此,我也不完全理解你讨论的这种影响。 - BeeOnRope
1
或者像QEMU一样进行动态重新编译,将x86代码运行在ARM或PowerPC上,或者在具有类似内存模型的SPARC上运行。或者在运行二进制到二进制优化器之后。 - Peter Cordes
2
@Gilgamesz - 只要保留代码的语义保证,CPU可以像编译器重新组织和消除各种代码位一样去除栅栏。大多数CPU不会进行这种类型的“优化”,因为它们实际上没有编译阶段(但即使在循环缓冲区中也可以看到这些东西的提示,例如mov消除、展开等)。然而,已经有二进制重编译x86 CPU,如nVidia的Denver和Transmeta CPU,从x86机器码编译成另一个特定于CPU的指令集。 - BeeOnRope
1
这样的CPU肯定会进行各种“编译器般”的优化。请注意,删除第二个栅栏需要分析周围的代码:对于您展示的存储来说,sfence是多余的,但如果在此段之前/之后有NT存储,则可能会产生影响,因此它们通常可以被删除,但并非总是如此。另请参见此评论 - BeeOnRope
1
即使有屏障,也不能保证在序列的任何单次执行中,核心在提交“10”和“12”之间失去缓存行给读取器。我猜如果有足够多的核心向该核心发送RFO,你可以让它变得非常可能。但仍然是一个有趣的观点,即中断可能会打败存储合并。 - Peter Cordes
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接