IA X86/X86_64 load乱序实现

在乱序这个事情上已经纠结有两天之久了,基本上可以说从工作到现在没啥问题可以需要纠结这么久的,大概有相似经历的就是读书的时候通信里面OFDM,FFT,DFT之类的编码问题了。
所有的纠结其实就归结在写得上一篇blog上
http://blog.csdn.net/adambynes/article/details/52076298
后来觉得这可能就是写blog的好处吧,其实如果自己私底下想想这些问题如果错了就错了,但要是挂到网上了我觉得还是要对读者负责,所以其中对于load这块的乱序我真的是来来回回看了不少东西
memory-barriers.txt from linux kernel doc
IA64 memory ordering
64-ia-32-architectures-optimization-manual
64-ia-32-architectures-software-developer-manual
http://www.realworldtech.com/haswell-tm-alt/2/
基本上对于说memory ordering相关的部分都扫过了,其实在上一篇文章里面store这一块的乱序已经说了比较清楚了,因为store会用到一个store buffer的东西而这个东西就是在指令execution的时候把store的address和data放这个buffer里,到最后retirement(commitment)的时候再往cache里面写,所以store执行的时候虽然乱序了但是由于retirement是顺序因而write的所有对memory or cache的写自然而然就和program ordering一样了。 对于store store; load store的顺序;都可以用这个原理解释的很清楚。
可是到了load load这边似乎变得很怪异起来。因为手册里面说起来load load之间是不会乱序时,上下文很是模糊不清,只是说从程序角度来看,所以我只能做两种假设
1. 这个程序角度是指程序的causality,就是只要保证多核和单核的因果关系推理上不乱序即可。比如a=*x b=*y 虽然在乱序下b会被先赋值但是,这样的操作顺序不影响程序最终的结果。
2. 这个程序角度就是指访问总线的先后对于load其实基本认为就是执行指令的先后。
其实我个人一直偏向于第一种假设,但是manual里面有一个case我感觉和第一种假设违背,下面我们来看下这个case

developer manual 8.2.3.3
processor 0 processor 1
mov [_x], 1 1 move r1[_y] 3
mov[_y], 1 2 move r2[_x] 4
initially x=y=0
r1=1 and r2=0 is not allowed
首先p0上两个store必然是顺序的而且在p1上看到的顺序也是和p0操作一样的,所以r1=1 r2=0的情况只可能是 4 1 2 3的情况,但是手册上明确说了这种情况是不存在的,那不是就说明了load乱序执行的推理是不对的吗。

那如果认为x86上的非乱序load执行是约束执行的时间那问题就来了

  • 如果是严格的执行时间上的先后约束,那大家都知道IA上一般两个load单元,那必然只能用一个啊,不然怎么严格控制执行上的先后
  • 如果控制了执行上的先后,如果一条load指令在VA-PA的转化上发生了TLB miss外加cache miss那第二条load指令要等到何年何日才能执行啊
  • 结合以上两条基本可以判断这样的设计这个CPU的performance会有多差,所以这种设计方式直接就out。
  • 但是前面摆出的那个case又如何解释呢

终于在介绍haswell微架构的文章里面找到了答案

Because of the strong x86 ordering model, the load buffer is snooped
by coherency traffic. A remote store must invalidate all other copies
of a cache line. If a cache line is read by a load, and then
invalidated by a remote store, the load must be cancelled, since it
potentially read invalid data. The x86 memory model does not require
snooping the store buffer.

有了以上解释终于可以大彻大悟因为load buffer会作为cache agent去做cache的一致性,
developer manual 8.2.3.3
processor 0 processor 1
mov [_x], 1 1 move r1[_y] 3
mov[_y], 1 2 move r2[_x] 4
initially x=y=0
r1=1 and r2=0 is not allowed
所以当4 1 2 3时 4虽然先把0的数值放到了load buffer里面但是指令1里面的操作会是load buffer invalid掉,所以等到4 retirement的时候会重新去load一把y,因而虽然第一次load是4 3的顺序,但是retirement的时候又变成了3 4

这样load的乱序执行,在commit/retirement阶段就成了顺序了。

而且这样的推理能解释这个为什么需要lfence了
有兴趣的可以看看这个帖子
does it make any sense insturction lfence in x86
其实在做load如果是执行严格顺序的假设时我想到了这个问题,如果执行load是顺序,那要lfence干毛球,上面那个贴子讨论了不少,亮点不少。
因为lfence里面有明确说lfence前面的load一定要complete,所以如果假设乱序是执行时存在的,那这个lfence就有作用了。
但是前面不是已经说了没有lfence这个乱序在程序逻辑上也是对的啊。那加了lfence不是对performance就有影响了吗,我只能说我个人感觉正常的system memory的访问都不需要加lfence,唯一在有side effect的MMIO的memory上需要加,并且此时这种MMIO没有设成uncachable, 同时prefetch功能关闭的情况下才需要加lfence

你可能感兴趣的:(CPU微架构,hardware)