IA 架构下memory ordering的一些思考

之所以要写这篇东西是因为想把之前在公司的一些问题总结一下,在内存相关操作中一些ordering和dependency的问题一直没能太搞明白,所以最近空闲下来静下心研究了一下,觉得还是有所收获,期待各路IA高手们来进行拍砖。一直存在的问题有如下三个

  • memory ordering究竟是指的什么ordering
  • 为什么在IA强ordering的情况下RAR还要加lfence
  • 为什么load相关的操作一直只需要一个port

memory ordering

what is memory ordering这个问题真的是困惑了我好久。

from the IA developer manual volume3 chapter8.2 we can find the definition

*The term memory ordering refers to the order in which the processor issues reads(loads) the writes(store) through the system bus to system memory

然后在manual里面就指出了其实在单processor的系统下面有如下ordering的规则

  1. 两个read之间是不能乱序的
  2. write after write是不能乱序的
  3. write after read 是不能乱序的
  4. read after write是不能乱序的(此时read和write的地址一致)

初看不会去细想这个乱序究竟是什么,但是当要深入去思考其out of order的一些机制时就发现不得不考虑以上的问题。

下面我就给大家说下order的概念。

其实order要分为不同的层面来看
1. 指令被schedule的order
这边的被schedule的指令是后端在sheduler buffer(reservation station)里面等待被调度到执行单元的指令。等待调度的基本条件是src operand is ready,所以这边就会有调度时候的out of order,但是除了要满足src operand ready的条件之外,还会考虑以上load之间不能乱序的限制,所以如果上一条load没有执行完(此处可以没有retirement) ,下一条load是不会被调度的
2. 指令执行完的order
在执行的时候如果发生了cache miss, bank conflict, cache replacement等意外情况,read的读取会变得比较慢,如果恰巧下一个load or read是cache hit的这个时候就会使得这个load会先执行完。
所以在执行的时候必然会有out of order。
3. 指令回收(retirement时候的order)
所有的指令在retirement的时候都是in order的不会out of order。这边的指令回收对于load来说就是使某个寄存器有效,有效就是如果这个寄存器(其实准确是register file) 作为下面指令的源寄存器则下面的指令就可以考虑被调度了,但是对于store来说回收的时候会进行system bus或cache 的访问去写回数据。因为store在执行环节是先将数据写到store buffer里面的,等于是store的一个缓冲区,在回收环节才发生真正的写操作。

一般把程序里面指令的顺序称为program ordering,较老的CPU的memory ordering(此处认为是指令访问system bus的顺序对于load是执行环节,store是retirement环节)如pentium,486都是program ordering。但是pentium 4开始后都是采用processor ordering,所谓processor ordering就是说memory ordering于program ordering不一样但是CPU会处理这两者之间的差别使得最终的causality保持正确,但是这种causality是指单核上的。
我们来看下processor ordering它遵循的条款就是文章开头的四条,其实最主要的区分processor和program ordering的主要地方就是在processor ordering里允许load可以和前面的store乱序执行。但是这种乱序显然是对causality没有影响,不过对于多核就会有影响了。这个会在barrier相关的文章里面进行分析。

Why need lfence for RAR

翻看kernel关于memory barrier 关于IA的code,大家可以看到wmb的展开为空 但是rmb的展开是lfence不为空

#ifdef CONFIG_X86_OOSTORE
/* Actually there are no OOO store capable CPUs for now that do SSE, 
   but make it already an possibility. */
#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
#else
#define wmb()   __asm__ __volatile__ ("": : :"memory")
#endif
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)

好奇心强的同学会说这个不符合情理啊上一节刚说过RAR不是不会out of order的那为什么要加特殊指令来保护呢

说一下加memory barrier就是防止乱序的但是大家要分清楚乱序产生的危害分为local(single processor)和remote(multiple processors)的
先看local的case:
1.read a then read b如果a 和 b是MMIO的话,并且有side effect,则如果b先被执行,a后执行就可能直接对结果有影响。
2.read a then read b如果a 和 b是普通的system memory(AKA DRAM),同1一样分析但是此处没有side effect所以没有问题(这边有个问题就是load之间不乱序究竟是指执行时还是commitment的时候,这个问题需要重写一篇blog来解释它)
3.那write after write是怎么保证ordering的呢,首先执行的时候write or store是可以乱序的但是store不是直接写到cache or mem里面,是写到一个叫store buffer的地方,所以执行的时候乱序只是说写到SB里面的顺序是乱序的。回写到cache里面的时候发生在retirement的阶段,前面已经说过了retirement的时候是按序的,所以WAW是in order的。
4. 同3write after read也可以解释为什么可以in order还是由于有store buffer。
5. 那read after write如何来保证当操作同一个地址的时候能够in order
首先还是一样的分析执行的时候可以out of order所以read会读到较老的数据,导致write想更新的数据被没有被read catch到,看似这边就有问题了,但是IA在load的时候同样使用了一个叫load buffer的东东,load的操作会被放在load buffer里面,当store在执行的的时候,除了写store buffer,CPU还会检查load buffer里面是不是有相同地址的操作如果有并且这个指令的timestamp是在store之后的就会给这个load操作打上个标志位,让它在retirement的时候看情况决定是不是需要重新去执行一遍,这里有个store to load forwarding的应用就是说load不需要去store对应的cache里面去拿data因为这个过程会要等到store这个指令retirement,所以在此之前可以直接去store buffer里面读相应的内容。

说了这么多废话我们来看一个多CPU的case来解答我的第二个问题
这个例子基本会在很多奖rmb的地方用到
Let’s take a look at the pesudo code below:
processor A

read a
if( a == 2)
read b

processor B
store 1 –> b
store 2 –>a

original a = 0; b=0;
网上的分析如下:如果这个时候A上read发生out of order则会有可能get到
a=2 b=0的情况
此时需要在read b上面的语句中加上rmb使得 read b 不能跨越rmb去执行
所以必然会先read a then read b
看似这边要加rmb但是rmb是一个architecture independent的函数,所以还是要看具体架构实习,如果放在IA上由于read 和read肯定不会乱序,所以rmb的展开应该为空, 看来问题还是没解决。所以经过多方查找发现有人说lfence都是多余的
http://stackoverflow.com/questions/20316124/does-it-make-any-sense-instruction-lfence-in-processors-x86-x86-64?s=1|6.3398
我唯一的解释就是当如果两个load都是真对的是MMIO但是此MMIO又没有设置成UC类型的情况下需要lfence。

why load operation only need one port

最后来搞个轻松点的
看过IA micro architecture的人都会注意到load都只需要一个port或是说只需要一个运行单元,而store需要两个一个为store addr一个为store data,我看了manual我觉得这边的store 和 load除了做需要从VA拿到PA,还有件重要的事情就是要操作load buffer 和 store buffer,这个上一小节就已经详细说过了,之所以load需要一个addr和一个data运算单元是应为store buffer里面会存address 和 data,而load buffer 里面我估摸只有address, 那data存在哪里呢就是在register file里面但是这个register file只有retirement的时候才会ready。

link:
https://en.wikipedia.org/wiki/Memory_disambiguation

https://en.wikipedia.org/wiki/Out-of-order_execution

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