[英]How does memory barrier work?
在Windows下,有三個編譯器內部函數來實現內存屏障:
1. _ReadBarrier;
2. _WriteBarrier;
3. _ReadWriteBarrier;
但是,我發現了一個奇怪的問題:_ReadBarrier似乎是一個無效的虛函數! 以下是VC ++ 2012生成的匯編代碼。
我的問題是:如何在匯編指令中實現內存屏障功能?
int main()
{
013EEE10 push ebp
013EEE11 mov ebp,esp
013EEE13 sub esp,0CCh
013EEE19 push ebx
013EEE1A push esi
013EEE1B push edi
013EEE1C lea edi,[ebp-0CCh]
013EEE22 mov ecx,33h
013EEE27 mov eax,0CCCCCCCCh
013EEE2C rep stos dword ptr es:[edi]
int n = 0;
013EEE2E mov dword ptr [n],0
n = n + 1;
013EEE35 mov eax,dword ptr [n]
013EEE38 add eax,1
013EEE3B mov dword ptr [n],eax
_ReadBarrier();
n = n + 1;
013EEE3E mov eax,dword ptr [n]
013EEE41 add eax,1
013EEE44 mov dword ptr [n],eax
}
013EEE56 xor eax,eax
013EEE58 pop edi
013EEE59 pop esi
013EEE5A pop ebx
013EEE5B add esp,0CCh
013EEE61 cmp ebp,esp
013EEE63 call __RTC_CheckEsp (013EC3B0h)
013EEE68 mov esp,ebp
013EEE6A pop ebp
013EEE6B ret
_ReadBarrier
, _WriteBarrier
和_ReadWriteBarrier
是影響編譯器重新排序代碼的內在函數 ; 它們與CPU內存屏障完全無關,僅對特定類型的內存有效(請參閱此處的 “受影響的內存”)。
MemoryBarrier()
是用於強制CPU內存屏障的內在函數。 但是,Microsoft的建議是使用std::atomic<T>
繼續使用VC ++。
現代處理器能夠在實際“完成”指令之前執行相當長的指令,因此在涉及某些類型的存儲器操作時,使用內存屏障來阻止它運行到很遠的地方,其中嚴格的排序是必需 - 對於大多數事情,如果你在變量b之前寫入變量a,或者在a之前寫入b,則實際上並不重要。 但有時它確實如此。
x86指令集有lfence
, sfence
和fence
,它們分別是“fence in”加載,存儲和所有內存操作。 關於“柵欄”或“屏障”指令的要點是確保屏障指令之前的所有指令在屏障可以繼續之后的下一指令之前完成了它們的加載,存儲或兩者。
例如,如果要實現信號量,互斥量或類似指令,這一點很重要,因為在繼續讀取其他數據之前,存儲值“我已鎖定信號量”非常重要。 換句話說,事情可能會出錯。
請注意,除非您真的知道自己在處理內存障礙方面做了什么,否則最好不要使用它們 - 並且依賴已經解決相同問題的現有代碼 - std::atomic
是一個資助此類代碼的地方。 我寫了很多“棘手的”代碼,但只有一兩次我的代碼中需要一個內存屏障。
有好幾次,我需要讓編譯器不要傳播代碼,你可以用“無操作函數”來做,顯然現在甚至還有特殊的內部函數來做。
有幾個要點需要考慮。 也許第一個障礙只是在多線程代碼中產生影響,並且大多數編譯器需要一個特殊選項來生成多線程代碼。 像_ReadBarrier
這樣的東西幾乎肯定是編譯器內置_ReadBarrier
,除非你給出了多線程代碼的選項,否則它什么都不做。
第二,即使在多線程環境中,硬件所需的內容也各不相同。 在我工作的大多數機器上(大約四十年),機器從不需要任何東西; 只有當機器具有復雜的讀寫流水線時,障礙才會變得相關。 (大多數早期的機器甚至沒有圍欄或屏障指令,因此生成的代碼必須為空。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.