C/C++ 编程中的内存屏障(Memory Barriers) (1)
明天就要transfor去做檢索引擎了,今天閑下來了,更新一下博客哈。之前 @高V 同學對本人之前《代碼技巧及優化(c/c++)》的文章第六條,有關cache命中和cpu流水優化比較感興趣,也提出了一些他的看法,今天,我就細化的說一下某些編程的點 -- 內存屏障,以及內存屏障對代碼的影響。
OK,首先來說一下什么是"內存屏障",可以先看一下官方式的說法http://www.kernel.org/doc/Documentation/memory-barriers.txt,內存屏障其實就是因為編譯器優化和CPU對寄存器和cache的使用,導致對內存的操作不能夠及時的反映出來,比如cpu寫入后,讀出來的值可能是舊的內容。舉個例子,對一個變量賦值然后讀出它的值這一看似“原子”的操作,因為內存是沒有ALU計算單元的,所以內存沒有計算的能力。而CPU一般情況下是不直接讀寫內存的(emmintrin.h應用例外),所以這一個過程可以看作(編譯器優化后):
讀取內存數據到cache --> CPU讀取cache/寄存器 --> CPU的計算 --> 將結果寫入cache/寄存器 --> 寫回數據到內存
有人可能會問,為什么要這么麻煩,因為是編譯器優化和CPU優化的結果,內存的時延比CPU高的多,是10ns級別,所以會通過讀寫寄存器或拆cache優化。這有可能導致一個問題,cache中的數據和內存實際的數據不一致,當多線程情況下,有可能會讀"臟數據",或者帶來線程執行結果不一致。這就是所謂的“內存屏障”(但不僅限于此case)。
網上有幾篇文章提到的《獨辟蹊徑品內核》中的代碼,本人寫了一個近似版本如下:
[cpp]
view plaincopyprint?
include<stdlib.h>#include<stdio.h> #include<pthread.h>
#include<unistd.h> //globalvariable intflag=1;void*wait(void*context){while(flag){printf("continue\n");sleep(1);}}voidwakeup(){flag=0;}intmain(intargc,char*argv[]){pthread_tpid=0;pthread_create(&pid,NULL,wait,NULL);sleep(3);wakeup();printf("Done\n");getchar();}
按照書中的說法,flag是被其他線程意外修改的話。while循環會被編譯器優化,編譯器在發現wait函數里并沒有修改flag,所以就會對flag進行cache,放在eax寄存器中,內存中的flag實體如果被修改,eax寄存器不會感知。自己試了一下,在沒有優化和Gcc O2的情況下程序正常跳出了循環,可能是因為cpu或者linux新版內核或者gcc的新編譯特性所致(如果哪位同學了解,可以留言哈)。之后會跟進這個問題,如果有發現,我會貼出來哈。
其實,通過gcc -S看到匯編過程的中間匯編代碼也可以看出寫東西:
[cpp]
view plaincopyprint?
wait:.LFB46:.cfi_startprocsubq$8,%rsp.cfi_def_cfa_offset16movlflag(%rip),%edxtestl%edx,%edxje.L4.p2align4,,10.p2align3.L5:movl$.LC0,%edicallputsmovl$1,%edicallsleep<SPANstyle="COLOR:#ff6600">movlflag(%rip),%eaxtestl%eax,%eaxjne.L5</SPAN>.L4:addq$8,%rsp.cfi_def_cfa_offset8ret.cfi_endproc.LFE46:.sizewait,.-wait.p2align4,,15.globlwakeup.typewakeup,@function
(我不是匯編大牛,錯了別炮轟哈),其中標紅的語句16~18行可以看出,循環只是檢測eax寄存器是不是0(不太熟悉匯編的朋友可能會為什么test %eax,%eax,這只是一個優化因為與操作比cmp要快),可以看出,確實是在不斷的讀寄存器而不是內存。
到此,大家對“內存屏障”估計也有了一個初步的認識,內存屏障主要分三類:編譯器優化(如上case) / 緩存優化 / CPU亂序執行(后面文章會提到)
大家可能會問,那豈不這樣會造成很多問題。其實不一定,據本人的知識范圍,大部分內存屏障導致的問題都出現在內核態,用戶態需要注意的方面不多。而且用戶也有相應的解決方案,最簡單的就是鎖機制,還有volatile關鍵字,可以把可能出現的cache讀臟的數據volatile int tmp = 0;這樣每次操作都會從內存獲取。
之后的文章會深入一下“內存屏障”和CPU亂序的問題。緩存優化導致的內存屏障,基本在新的硬件上得到了比較好的解決,而且在用戶態下基本感知不到。只要大家合適的用好多線程的鎖機制和volatile的正確運用即可。
總結
以上是生活随笔為你收集整理的C/C++ 编程中的内存屏障(Memory Barriers) (1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis集群的搭建
- 下一篇: leaflet 如何绘制圆