Memory Ordering in Intel Family

前言:这篇文章是翻译自的Intel x86/64的规范文档,其中第8.2节中有关Memory Ordering的几段。整个文档是一本巨书,这里可以download:http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-system-programming-manual-325384.pdf
因为我们有必要了解一下CPU对memory ordering的规范说明,这里我们看看Intel的。

2.1 MEMORY ORDERING

术语memory ordering指的是,processor以何种顺序通过系统总线发起到系统内存的读(loads)和写(stores)操作。基于体系结构的实现,Intel64和IA-32支持好几种memory-ordering模型。比如Intel386处理器强制执行程序顺序(通常被称为strong ordering(强排序)),在任何情况下,对系统总线发起的读和写都是根据他们在指令流中的顺序进行的。
为了优化指令执行的性能,IA-32体系结构允许违背strong ordering模型,在P4,Xeon和P6 processor家族中,这被称为processor ordering。这些processor-ordering变形(这里成为memory ordering模型)允许提升性能的而一些操作,比如允许读提升到缓存写的前面。这些变形的目的都是为了在维持内存conherency的基础上提升执行的速度,甚至在多核系统中。

2.2 Memory Ordering in the Pentium and Intel486 Processors

Pentium和Intel486 processor遵循processor-ordered内存模型,然而在大多数情况下,它们都是按照strong-ordered processor运行的。在系统总线上的读和写通常都是程序序——除了如下的情况会展现出processor ordering。当所有的buffered write cache命中时,Read misses允许被提升到对系统总线的buffered writes之前,因此buffered write不会同时指向被read miss访问的同一块地址上(因为buffered write cache命中)。
在I/O操作的情形,读和写都是根据程序序。
期望在processor-ordered processor上正确执行的软件不应该依赖Pentium和Intel486的相对strong ordering。相反,应该保证对共享变量的访问如果要控制processor之间的并发执行,要显式的使用lock和序列化操作来保证程序序。

2.3 Memory Ordering in P6 and More Recent Processor Families

Intel Core 2Duo,Atom,Core Duo,Pentium4和P6家族使用的processor-ordered memory-ordering模型,又可以被进一步定义为:write ordered with store-buffer forwarding。这种模型的特性如下面的描述:
在single-processor系统中对于被定义为write-back cacheable的内存区域,memory-ordering模型有如下的原则(注意对于single和multiple processor的memory-ordering原则都是从在processor上执行的软件的角度来说的,这里的属于processor指的是logical processor。比如,一个支持multiple core或者Intel Hyper-Threading计数的物理processor也被当做multi-processor系统看待):

1 读和读不会重排序;Reads are not reordered with other reads
2 写和older读不会重排序;Writes are not reordered with older reads;
3 写和写不会重排序;Writes are not reordered with other writes,除了如下情况:
— 使用nonn-temporal move指令(MOVNT1, MOVENTQ,MOVNTDQ等)的streaming
stores(writes)
— string操作(见8.2.4.1)
4 Reads可能会和对不同地址的older writes重排序,但是同一地址不会(prefetch?)
5 Reads不能越过前面的LFENCE和MFENCE指令;
6 还有其它规则,不一一列举了,直接看文档;

在multiple-processor系统上应用了如下的规则:

1 单processor使用和single-processor系统相同的规则
2 一个processor的writes会按照相同的顺序对所有的processor可见
3 一个processor的writes不要求与其它processor的writes有序
Writes from a single processor are NOT ordered with respect to
the writes from other processors
4 Memory ordering遵从因果关系(memory ordering遵守传递可见性)
Memory ordering byes causality (memory ordering respects transitive visibility)
5 Lock指令是全序的,Locked instructions have a total order

2.4 Examples Illustrating the Memory-Ordering Principles

本节提供几个例子来说明8.2.2节中介绍的memory-ordering原则下的行为;可以帮助软件编写者明白memory ordering可能影响指令不同顺序时的结果。
这些例子仅限于访问被定义为write-back cacheable(WB)的内存区域(8.2.3.1节描述了其它的一些限制)。读者应该明白它们只是描述了对软件可见的行为。一个logic processor也可能重排两个access,即使一个例子表示他们可能没有重排。
这个例子只是说明软件不能检测出这样的一个重排发生了。类似的,一个logic processor可能会多次执行一个内存access操作,只要对软件来说这个行为和只执行一次内存access的结果是一致的。

2.4.1 Assumptions, Terminology, and Notion

前面说过的,本节中的例子仅限于访问被定义为write-back cacheable(WB)的内存区域。它们只应用在通常的loads stores,已经locked read-modify-write指令。并不应用于如下的几种情况:string指令的out-of-order stores(8.2.4节);等等,后面集中不翻译了。

本节例子的基础原则适用于单个的memory accesses和locked read-modify-write指令。Intel-64 memory-ordering模型保证:下面的每一种memory-access指令,其内存操作看来就像执行一个单个的内存访问。

1 读写单个byte;
2 读写单个word(2 bytes),地址对齐在2 byte;
3 读写单个double word(4 bytes),字节对齐在4 byte;
4 读写单个quadword(8 bytes),字节对齐在8byte;

任何locked指令(XCHG或者其它任何待LOCK前缀的read-modify-write指令)看起来就像执行不可见的不可中断的顺序:load(s) followed by store(s),并且忽略对齐。
其它指令可能会实现成多个内存访问。从memory-ordering的角度看,并且没有相对顺序的保证。

8.2.3.2到8.2.3.7给了使用MOV指令的例子,这些例子中的原则同样用于通用的load和store,以及其它从内存load或者向内存的store的指令。

本节中的术语“processor”代表的是logic processor,例子是基于Intel-64汇编语言语法,并且使用如下的符号约定:

1 使用r开头的变量,比如r1和r2,代表了寄存器;
2 内存地址用x,y,z表示;
3 Store操作写作 mov [_x], val,表示将val存储到地址x中;
4 Load操作写作mov r, [_x],表示读取地址x中的内容并保存到寄存器r;

再说一遍,例子仅仅代表从软件的角度看到的行为,当后面的小节说“这两个stores被重排序了”,指的是“从软件的角度看,这两个stores重排序了”。

2.4.2 Neither Loads Nor Stores Are Reordered with Like Operations

在Intel-64 memory-ordering模型中,loads和loads之间,以及stores和stores之间是绝对不允许重排序的。也就是说,它保证,loads和程序序一致,stores和程序序一致;比如如下的例子:

| Processor 0            |  Processor 1  |
| mov [_x], 1            |  mov r1, [_y] |
| mov [_y], 1            |  mov r2, [_x] |

初始值x = y = 0,执行结果r1=1,r2=0是不允许的。
这种不允许的结果只有在如下的两种情况下才会出现:
1 processor0的两个stores重新排序了;(processor1的两个loads在它们中间执行)
2 或者processor1的两个loads重新排序了;(processor0的两个stores在它们中间执行)

如果r1=1,那么对y的store操作早于对y的load操作。(继续)因为Intel-64 memory-ordering模型不允许store之间重排序,所以对x的store早于对y的load。因为也不允许loads之间重新排序,所以对x的store也早于对x的load,因此r2=1。

2.4.3 Stores Are Not Reordered With Earlier Loads

Intel-64 memory-ordering保证,对于同一个processor,store操作不能在在它前面的load之前执行;用如下的例子说明:
例子:

|  Processor 0              |  Processor 1
|  mov r1, [_x]             |  mov r2, [_y]
|  mov [_y], 1              |  mov [_x], 1

初始值x = y = 0,执行结果r1=1,r2=1是不允许的。

假设r1 = 1;

> 因为r1=1,processor1的store to x早于processor0的load from x;
> 因为Intel-64 memory-ordering模型阻止在同一个processor上store与它前面的loads重新排序, 所以processor1的load from y早于它的store to x;
> 同样的,processor0的load from x早于它的store to y;
> 所以,processor1的load from y早于processor0的store to y,暗示r2=0;

2.4.4 Loads May be Reordered with Earlier Stores to Different Locations

这就是前面文章中的那个例子,Intel-64 memory-ordering模型下,load可以和前面的store重新排序,但是只能是对不同的地址。
例子1:

Processor 0              |  Processor 1
mov [_x], 1              |  mov [_y], 1
mov r1, [_y]             |  mov r2, [_x]

初始值x = y = 0,执行结果r1=0,r2=0是允许的。
在每一个processor上,load和store都是操作不同的地址,因此可以重排序。操作的任意交叉都是允许的,当两个loads早于两个stores执行时,就会出现r1=0和r2=0的结果。

对于同一个地址,则不允许,如下面的例子2:
Processor 0
mov [_x], 1
mov r1, [_x]
初始值x=0,那么r1=0是不允许的。
很显然如果mov r1, [_x]先执行,即使对于单线程程序,整个结果也是错误的。也就是前面的例子1在设置cpu affinity后,不会再出现r1=0和r2=0的结果。
虽然是两个线程,但是对于processor而言,对同一内存地址,load不能前面的store重排序。(对于processor其实是没有线程的概念的,在processor的视角上,只是对内存的store和load指令序列)

2.4.5 Intra-Processor Forwarding is Allowed

Intel-64 memory-ordering模型允许两个processor的并发store时,这两个processor看到的顺序可能互不不同。特别的,每一个processor可能认知它自己的store发生在另一个processor之前。用如下的例子说明:

Processor 0            |  Processor 1
mov [_x], 1            |  mov [_y], 1
mov r1, [_x]           |  mov r3, [_y]
mov r2, [_y]           |  mov r4, [_x]

初始值x = y = 0;结果r2 = r4 = 0是允许的。
Memory-ordering对于两个processor执行的两个stores的顺序没有任何的限制。于是processor0看到自己的store早于processor1的,而processor1则看到自己的store早于processor0的(每一个processor都是自我一致性的),这就允许r2=r4=0;

在实际中,本例的这种重排现象会导致store-buffer forwarding这一结果。当store被临时的放在一个processor的store buffer中,这可以满足processor自己的load,但是却不能被其它的processor看到,因而不能load最新的结果。

2.4.6 Stores Are Transitively Visible

Intel64保证store的可见性是传递的,stores that are causally related appear to all
processors to occur in an order consistent with the causality relation。下面的例子:

Process 0             | Processor 1               | Process 2
mov [_x], 1           | mov r1, [_x]              |
                      | mov [_y], 1               | mov r2, [_y]
                      |                           | mov r3, [_x]

初始值x = y = 0; r1=1, r2 = 1, r3 = 0是不允许的;
假设r1 = r2 = 1;

>因为r1 = 1,processor0的store早于processor1的load;
>因为Intel64不允许store和前面的load重排序(见2.4.3),processor1的load早于它的store;因此processor0的store早于processor1的store;
>因为processor0的store早于process1的store,memory-ordering模型保证了从所有processor的角度看,processor0的store早于processor1的store;
>因为r2 = 1,processor1的store早于processor2的load;
>因为Intel64阻止了load之间的重排序,processor2的load是根据次序发生的;
>上面的条目暗示了processor0对x的store发生于processor2从x的load,因此r3 = 1;

2.4.7 Stores Are Seen in a Consistent Order by Other Processors

就像2.4.5节提到的,对于两个processor的stores,memory-ordering允许它们看到不同的顺序。然而对于它们之外的其它的processor而言,这两个store的执行顺序必须看起来是一致的。用下面的例子说明:

Processor 0   | Processor 1    | Processor 2    | Processor 3
mov [_x], 1   | mov [_y], 1    | mov r1, [_x]   | mov r3, [_y]
                               | mov r2, [_y]   | mov r4, [_x]

初始值x = y = 0,r1 = 1, r2 = 0, r3 = 1, r4 = 0是不允许的。
基于2.4.3的原则:

>processor2的两个load不能重排序;
>processor3的两个load不能重排序;
>如果r1 = 1, 并且r2 = 0,那么在processor2看来,processor0的store早于
processor1的store;
>同样的,r3 = 1并且r4 = 0暗示了processor1的store早于processor0的store
(注:这里应该是从processor3的角度)
因为本条规则保证了:对于其它的processor,它们看到的任何两条store的执行顺序都需要是一致的;那么这个结果无疑违反了这个原则。

好了,后面还有其它的几个例子,不列举了,到这里应该足够了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值