"Loads are not reorderd with other loads" is a FACT!! 再續(xù):.NET MM IS BROKEN
上一篇隨筆“"Loads are not reorderd with other loads" is a FACT!! 續(xù):不要指望 volatile”中已經(jīng)提到了。.NET的內(nèi)存模型在volatile load上的實(shí)現(xiàn)是錯(cuò)誤的。這在今天終于是半個(gè)官方的結(jié)論了。有關(guān)這個(gè)討論的結(jié)論可以參考“A bit more formalism as to why CLR's MM is broken on x86/x64 ”。
關(guān)于內(nèi)存模型(MM)的問(wèn)題枯燥,縮略詞跟別的領(lǐng)域有過(guò)之無(wú)不及,為了便于說(shuō)明趁著這個(gè)機(jī)會(huì)羅列一下。
內(nèi)存模型(MM),實(shí)際上我們關(guān)注的是位于多處理器共享內(nèi)存處理器的內(nèi)存一致性模型(Memory Consistency Model for Shared Memory Multi-Processor)之上的,由某一個(gè)標(biāo)準(zhǔn)規(guī)定的軟的內(nèi)存模型。例如可以運(yùn)行托管代碼的.NET虛擬機(jī),也像一個(gè)真正的物理的計(jì)算機(jī)一樣在管理內(nèi)存,那么對(duì)內(nèi)存讀寫時(shí)其的行為是什么樣的呢,這就需要內(nèi)存模型來(lái)規(guī)定(這是一個(gè)軟的內(nèi)存模型)。
好了,內(nèi)存模型對(duì)于多線程的影響主要有這么兩個(gè)問(wèn)題,第一個(gè)問(wèn)題是次序調(diào)整問(wèn)題,也就是memory reorder問(wèn)題。第二個(gè)問(wèn)題是可見(jiàn)性問(wèn)題。這兩個(gè)問(wèn)題是有關(guān)聯(lián)的,但是必須分開!一會(huì)兒就會(huì)看到他們實(shí)際上不是一回事。
為什么要reorder,而不是干脆順序執(zhí)行算了,厄~這是因?yàn)槟軌蜃畲笙薅鹊睦锰幚砥鞯男阅芎唾Y源。當(dāng)然,這種特性使得多線程程序少不留神就會(huì)引入Bug(在《程序員》上看到一個(gè)笑話,說(shuō),如果讓程序員來(lái)做汽車,那么現(xiàn)在汽車百公里內(nèi)油耗將是原來(lái)的十分之一,而性能確實(shí)現(xiàn)在的十倍。但有一點(diǎn),這汽車肯定一年爆炸一次,無(wú)一幸免)。
memory reorder在現(xiàn)代處理器中是非常常見(jiàn)的。例如,對(duì)于Intel x86處理器來(lái)說(shuō),有如下規(guī)則(詳見(jiàn)Intel Memory Ordering 白皮書):
(1) load操作不會(huì)和其他load操作調(diào)整次序
(2) store操作不會(huì)和其他store操作調(diào)整次序
(3) store操作不會(huì)和之前的load操作調(diào)整次序
(4) load可能會(huì)和之前的store操作調(diào)整次序,但僅僅限于操作不同位置的內(nèi)存。如果操作的內(nèi)存相同,則不會(huì)交換。
(5) 在多處理器系統(tǒng)中,內(nèi)存操作次序調(diào)整代表了可見(jiàn)性的次序(注意,僅僅是次序,并不代表這種次序立桿見(jiàn)影的體現(xiàn)出來(lái)。對(duì)于這種情況,參見(jiàn)Intel白皮書內(nèi)第2.4或參見(jiàn)前一篇隨筆)
(6) 在多處理器系統(tǒng)中,對(duì)于相同位置的store不會(huì)調(diào)整。
(7) 在多處理器系統(tǒng)中,lock修飾的指令不會(huì)調(diào)整。
(8) 所有被lock修飾的load和store都不會(huì)調(diào)整。
好了,以上就是memory reorder的內(nèi)容了。下面的問(wèn)題是可見(jiàn)性的問(wèn)題。讓人瘋狂的是,memory指令的結(jié)束并不意味著可見(jiàn)性。為什么,厄~這是CPU內(nèi)存結(jié)構(gòu)優(yōu)化的結(jié)果。我們可以另寫一篇文章了。
因此,memory reorder的內(nèi)容對(duì)于另一個(gè)(邏輯)處理器來(lái)說(shuō)可能不是真理,但是對(duì)自己來(lái)說(shuō),一定是可見(jiàn)的??上?,我們以前沒(méi)有意識(shí)到這個(gè)問(wèn)題。而是想當(dāng)然的認(rèn)為其有acquire或者release語(yǔ)義。(當(dāng)然除了lock的情況)
等等,什么叫做acquire和release語(yǔ)義。我們?cè)谶@里給出嚴(yán)格定義:
acquire語(yǔ)義:一個(gè)操作具有acquire語(yǔ)義,則系統(tǒng)中的其他(邏輯)處理器一定能夠在看到其后的命令的結(jié)果之前看到其結(jié)果。
release語(yǔ)義:一個(gè)操作具有release語(yǔ)義,則系統(tǒng)中其他(邏輯)處理器一定能夠看到在這之前的所有處理了的指令的結(jié)果。
可見(jiàn),acquire和release規(guī)定了指令允許調(diào)整的方向,即一個(gè)指令有acquire語(yǔ)義則其之前的指令可能調(diào)整到其后執(zhí)行,但是其后的指令一定不可能在它執(zhí)行之前執(zhí)行;一個(gè)指令有release語(yǔ)義,則其之后的指令可能調(diào)整到其前執(zhí)行,但是其前的指令一定不會(huì)調(diào)整到其后執(zhí)行。更進(jìn)一步的,acquire和release語(yǔ)義規(guī)定了可見(jiàn)性。這個(gè)是非常要命的。
好,明白了這個(gè),我們?cè)倏纯次覀冊(cè)嫉睦樱?/p>
X = 1; Y = 1;
R0 = X; R2 = Y;
R1 = Y; R3 = X;
我們假設(shè)X和Y都是volatile的。這樣,如果.NET的MM正確實(shí)現(xiàn),則所有的volatile load均有acquire語(yǔ)義,所有的store,不管是不是volatile的,都具有release屬性!由于相關(guān)性,X=1不可能和R0=X交換,并且,R0=X也不可能和R1=Y交換。又依照可見(jiàn)性,不論如何都不會(huì)出現(xiàn)全部執(zhí)行完畢之后R1=0,R3=0的結(jié)果??墒沁@個(gè)結(jié)果恰恰出現(xiàn)了!
好,那么我們?nèi)绾谓鉀Q可見(jiàn)性的問(wèn)題呢?答案是恰當(dāng)?shù)膍emory fence.參考<<Intel? 64 and IA-32 Architectures Software Developer's Manual>>
LFENCE:強(qiáng)制按順序執(zhí)行l(wèi)oad操作,其后的所有l(wèi)oad操作將等待直到先前的load全局可見(jiàn)!
SFENCE:強(qiáng)制按順序執(zhí)行store操作,其后的所有store操作將等待直到先前的store全局可見(jiàn)!
MFENCE:強(qiáng)制按順序執(zhí)行l(wèi)oad和store操作,其后的load或store操作等待直到先前的load或store全局可見(jiàn)!
慢,那LOCK呢,厄,實(shí)際上手冊(cè)上對(duì)于LOCK保證不會(huì)有多個(gè)處理器同時(shí)更新其內(nèi)容(不論是鎖定總線還是鎖定Cache)但是對(duì)于可見(jiàn)性的問(wèn)題只字未提。實(shí)際上,LOCK以及隱式的XCHG之類都沒(méi)有這種保證。我們來(lái)看一個(gè)InterlockedExchange的實(shí)現(xiàn):
2 {
3 long result;
4 volatile long* p = ptr;
5 __asm
6 {
7 __asm mov edx, p
8 __asm mov eax, value
9 __asm lock xchg [edx], eax
10 __asm result, eax
11 }
12 load_with_acquire(*ptr); // make sure it is visible
13 return result;
14 }
15
16 template <typename T>
17 static long load_with_acquire(const T& ref)
18 {
19 long to_return = ref;
20 _ReadWriteBarrier();
21 return to_return;
22 }
好了,問(wèn)題到此可以告一段落了。最后想要提醒一句,如果你的程序僅僅只有一個(gè)線程并沒(méi)有跨進(jìn)程的共享資源,你可以根本不關(guān)心這些問(wèn)題,因?yàn)閷?duì)于單線程來(lái)說(shuō),處理器中間雖然做了很多的“你不知道的事情”,但是結(jié)果“看上去”就好像和順序執(zhí)行下來(lái)的結(jié)果一樣;而對(duì)于多線程開發(fā)人員來(lái)說(shuō),使用同步對(duì)象或者InterlockedXxx之類的時(shí)候已經(jīng)隱式的包含了恰當(dāng)?shù)膍emory fence。因此大可不必?fù)?dān)心;而如果你是底層人員,那么你不得不著手解決這些繁瑣的問(wèn)題了。
上一篇隨筆“"Loads are not reorderd with other loads" is a FACT!! 續(xù):不要指望 volatile”中已經(jīng)提到了。.NET的內(nèi)存模型在volatile load上的實(shí)現(xiàn)是錯(cuò)誤的。這在今天終于是半個(gè)官方的結(jié)論了。有關(guān)這個(gè)討論的結(jié)論可以參考“A bit more formalism as to why CLR's MM is broken on x86/x64 ”。關(guān)于內(nèi)存模型(MM)的問(wèn)題枯燥,縮略詞跟別的領(lǐng)域有過(guò)之無(wú)不及,為了便于說(shuō)明趁著這個(gè)機(jī)會(huì)羅列一下。
浙公網(wǎng)安備 33010602011771號(hào)