優化屏障和記憶體壁壘

2021-05-22 13:55:22 字數 3411 閱讀 8908

當使用指令優化的編譯器時,你千萬不要認為指令會嚴格按它們在源**中出現的順序執行。例如,編譯器可能重新安排組合語言指令以使暫存器以最優的方式使用。此外,現代cpu通常並行地執行若干條指令,且可能重新安排記憶體訪問。這種重新排序可以極大地加速程式的執行。

然而,當處理同步時,必須避免指令重新排序。因為如果放在同步及原語之後的一條指令在同步原語本身之前執行,事情很快就會變得失控。所以,所有的同步技術都應該避免指令優化後的亂序,這裡用到乙個優化屏障和記憶體壁壘技術。

優化屏障(optimization barrier)技術保證編譯器不會混淆存放在同步操作之前的組合語言指令和存放在同步操作之後的組合語言指令,這些組合語言指令在c語言中都有對應的語句。

在linux中,優化屏障就是barrier()巨集,c編譯器它展開為asm volatile("":::"memory"):

#define barrier() __asm__ __volatile__("": : :"memory")

指令的具體內容其實是乙個空的指令,asm告訴編譯程式要插入組合語言片段(這種情況下為空)。volatile關鍵字禁止編譯器把asm指令與程式中的其他指令重新組合。memory關鍵字強制編譯器假定ram中的所有記憶體單元已經被組合語言指令修改。

因此,執行優化屏障巨集後,編譯器不能使用存放在cpu暫存器中的記憶體單元的值來優化asm指令前的**。注意,優化屏障並不保證不使當前cpu把組合語言指令混在一起執行——避免cpu亂序執行是記憶體壁壘的工作。

記憶體壁壘(memory barrier)技術確保,在進入臨界區之後的操作開始執行之前,臨界區之前的操作已經完成。因此,記憶體壁壘類似於防火牆,讓任何組合語言指令都不能通過。在80x86處理器中,下列種類的組合語言指令是「序列的」,因為它們起記憶體壁壘的作用:

(1)對i/o埠進行操作的所有指令。

(2)有lock字首的所有指令。

(3)寫控制暫存器、系統暫存器或除錯暫存器的所有指令(例如,cli和sti,用於修改eflags暫存器的正標誌的狀態)。

(4)在pentium 4微處理器中引入的組合語言指令lfence、sfence和mfence,它們分別有效地實現讀記憶體壁壘、寫記憶體壁壘和讀-寫記憶體壁壘。

(5)少數專門的組合語言指令,終止中斷處理程式或異常處理程式的iret指令就是其中的乙個。

linux使用六個記憶體壁壘巨集,如下所示。當然,這些巨集也被當作優化屏障,因為我們必須保證編譯器不在壁壘前後移動組合語言指令。「讀記憶體壁壘」僅僅作用於從記憶體讀的指令,而「寫記憶體壁壘」僅僅作用於寫記憶體的指令。記憶體壁壘既用於多處理器系統,也用於單處理器系統。當記憶體壁壘應該防止僅出現於多處理器系統上的競爭條件時,就使用smp_***()巨集;在單處理器系統上,它們什麼也不做。其他的記憶體壁壘防止出現在單處理器和多處理器系統上的競爭條件。

mb( ):適用於 mp 和 up的記憶體壁壘

rmb( ):適用於 mp和 up的記憶體壁壘

wmb( ):適用於 mp和 up的記憶體壁壘

smp_mb( ):僅適用於mp的記憶體壁壘

smp_rmb( ):僅適用於mp的記憶體壁壘

smp_wmb( ):僅適用於mp的記憶體壁壘

#define alternative(oldinstr, newinstr, feature)            /

asm volatile ("661:/n/t" oldinstr "/n662:/n"             /

".section .altinstructions,/"a/"/n"        /

"  .align 4/n"                    /

"  .long 661b/n"            /* label */        /

"  .long 663f/n"          /* new instruction */    /

"  .byte %c0/n"             /* feature bit */    /

"  .byte 662b-661b/n"       /* sourcelen */    /

"  .byte 664f-663f/n"       /* replacementlen */    /

".previous/n"                    /

".section .altinstr_replacement,/"ax/"/n"        /

"663:/n/t" newinstr "/n664:/n"   /* replacement *//

".previous" :: "i" (feature) : "memory")

#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", x86_feature_xmm2)

#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", x86_feature_xmm2)

#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", x86_feature_xmm)

#ifdef config_smp

#define smp_mb()    mb()

#define smp_rmb()    rmb()

#define smp_wmb()    wmb()

#define smp_read_barrier_depends()    read_barrier_depends()

#define set_mb(var, value) do while (0)

#else

#define smp_mb()    barrier()

#define smp_rmb()    barrier()

#define smp_wmb()    barrier()

#define smp_read_barrier_depends()    do while(0)

#define set_mb(var, value) do while (0)

#endif

記憶體壁壘技術的實現依賴於系統的體系結構。在80x86微處理器上,如果cpu支援lfence組合語言指令,就把rmb()巨集展開為asm volatile("lfence") ,否則就展開為asm volatile("lock;addl $0,0(%%esp)":::"memory") 。asm指令告訴編譯器插人一些組合語言指令並起優化屏障的作用。lock;addl $0,0(%%esp) 彙編指令把0加到棧頂的記憶體單元;這條指令本身沒有價值,但是,lock字首使得這條指令成為cpu的乙個記憶體屏障。

intel上的wmb()巨集實際上更簡單,因為它展開為barrier()。這是因為intel處理器從不對寫記憶體訪問重新排序,因此,沒有必要在**中插入一條序列化彙編指令。不過,這個巨集禁止編譯器重新組合指令。

注意,在多處理器系統上,在前一博文「原子操作」中描述的所有原子操作都起記憶體屏障的作用,因為它們使用了lock位元組。

優化屏障和記憶體壁壘

當使用指令優化的編譯器時,你千萬不要認為指令會嚴格按它們在源 中出現的順序執行。例如,編譯器可能重新安排組合語言指令以使暫存器以最優的方式使用。此外,現代cpu通常並行地執行若干條指令,且可能重新安排記憶體訪問。這種重新排序可以極大地加速程式的執行。然而,當處理同步時,必須避免指令重新排序。因為如果...

優化屏障和記憶體屏障

優化屏障和記憶體屏障 優化屏障 編譯器編譯源 時,會將源 進行優化,將源 的指令進行重排序,以適合於cpu的並行執行。然而,核心同步必須避免指令重新排序,優化屏障 optimization barrier 避免編譯器的重排序優化操作,保證編譯程式時在優化屏障之前的指令不會在優化屏障之後執行。linu...

linux 優化和記憶體屏障

一 設定屏障的原因 我們程式設計時,指令一般不會按照它們在源程式的順序執行。原因是計算機為了提高程式執行的效能,會對它進行優化,這種優化主要有兩種 1.編譯器的優化 為了提高系統的效能,編譯器在不影響邏輯的情況下會調整指令的順序。2.cpu執行的優化 為了提高流水線的效能,cpu的亂序執行可能會讓後...