Memory barrier 簡介

来源:https://www.cnblogs.com/linhaostudy/archive/2018/07/16/9318276.html
-Advertisement-
Play Games

"Memory barrier" Memory barrier 簡介 程式在運行時記憶體實際的訪問順序和程式代碼編寫的訪問順序不一定一致,這就是記憶體亂序訪問。記憶體亂序訪問行為出現的理由是為了提升程式運行時的性能。記憶體亂序訪問主要發生在兩個階段: 1. 編譯時,編譯器優化導致記憶體亂序訪問(指令重排) 2 ...


Memory barrier

Memory barrier 簡介

程式在運行時記憶體實際的訪問順序和程式代碼編寫的訪問順序不一定一致,這就是記憶體亂序訪問。記憶體亂序訪問行為出現的理由是為了提升程式運行時的性能。記憶體亂序訪問主要發生在兩個階段:

  1. 編譯時,編譯器優化導致記憶體亂序訪問(指令重排)
  2. 運行時,多 CPU 間交互引起記憶體亂序訪問

Memory barrier 能夠讓 CPU 或編譯器在記憶體訪問上有序。一個 Memory barrier 之前的記憶體訪問操作必定先於其之後的完成。Memory barrier 包括兩類:

  1. 編譯器 barrier
  2. CPU Memory barrier

很多時候,編譯器和 CPU 引起記憶體亂序訪問不會帶來什麼問題,但一些特殊情況下,程式邏輯的正確性依賴於記憶體訪問順序,這時候記憶體亂序訪問會帶來邏輯上的錯誤,例如:

// thread 1
while (!ok);
do(x);
 
// thread 2
x = 42;
ok = 1;

此段代碼中,ok 初始化為 0,線程 1 等待 ok 被設置為 1 後執行 do 函數。假如說,線程 2 對記憶體的寫操作亂序執行,也就是 x 賦值後於 ok 賦值完成,那麼 do 函數接受的實參就很可能出乎程式員的意料,不為 42。

編譯時記憶體亂序訪問

在編譯時,編譯器對代碼做出優化時可能改變實際執行指令的順序(例如 gcc 下 O2 或 O3 都會改變實際執行指令的順序):

// test.cpp
int x, y, r;
void f()
{
    x = r;
    y = 1;
}

編譯器優化的結果可能導致y = 1x = r 之前執行完成。首先直接編譯此源文件:

g++ -S test.cpp

得到相關的彙編代碼如下:

movl r(%rip), %eax
movl %eax, x(%rip)
movl $1, y(%rip)

這裡我們看到,x = r 和 y = 1 並沒有亂序。現使用優化選項 O2(或 O3)編譯上面的代碼(g++ -O2 -S test.cpp),生成彙編代碼如下:

movl r(%rip), %eax
movl $1, y(%rip)
movl %eax, x(%rip)

我們可以清楚的看到經過編譯器優化之後 movl $1, y(%rip) 先於 movl %eax, x(%rip) 執行。避免編譯時記憶體亂序訪問的辦法就是使用編譯器 barrier(又叫優化 barrier)。Linux 內核提供函數 barrier() 用於讓編譯器保證其之前的記憶體訪問先於其之後的完成。內核實現 barrier() 如下(X86-64 架構):

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

現在把此編譯器 barrier 加入代碼中:

int x, y, r;
void f()
{
    x = r;
    __asm__ __volatile__("" ::: "memory");
    y = 1;
}

這樣就避免了編譯器優化帶來的記憶體亂序訪問的問題了(如果有興趣可以再看看編譯之後的彙編代碼)。本例中,我們還可以使用 volatile 這個關鍵字來避免編譯時記憶體亂序訪問(而無法避免後面要說的運行時記憶體亂序訪問)。volatile 關鍵字能夠讓相關的變數之間在記憶體訪問上避免亂序,這裡可以修改 x 和 y 的定義來解決問題:

volatile int x, y;
int r;
void f()
{
    x = r;
    y = 1;
}

現加上了 volatile 關鍵字,這使得 x 相對於 y、y 相對於 x 在記憶體訪問上有序。在 Linux 內核中,提供了一個巨集 ACCESS_ONCE 來避免編譯器對於連續的 ACCESS_ONCE 實例進行指令重排。其實 ACCESS_ONCE 實現源碼如下:

#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))

此代碼只是將變數 x 轉換為 volatile 的而已。現在我們就有了第三個修改方案:

int x, y, r;
void f()
{
    ACCESS_ONCE(x) = r;
    ACCESS_ONCE(y) = 1;
}

到此基本上就闡述完了我們的編譯時記憶體亂序訪問的問題。下麵開始介紹運行時記憶體亂序訪問。

運行時記憶體亂序訪問

在運行時,CPU 雖然會亂序執行指令,但是在單個 CPU 的上,硬體能夠保證程式執行時所有的記憶體訪問操作看起來像是按程式代碼編寫的順序執行的,這時候 Memory barrier 沒有必要使用(不考慮編譯器優化的情況下)。這裡我們瞭解一下 CPU 亂序執行的行為。在亂序執行時,一個處理器真正執行指令的順序由可用的輸入數據決定,而非程式員編寫的順序。

早期的處理器為有序處理器(In-order processors),有序處理器處理指令通常有以下幾步:

  1. 指令獲取
  2. 如果指令的輸入操作對象(input operands)可用(例如已經在寄存器中了),則將此指令分發到適當的功能單元中。如果一個或者多個操作對象不可用(通常是由於需要從記憶體中獲取),則處理器會等待直到它們可用
  3. 指令被適當的功能單元執行
  4. 功能單元將結果寫回寄存器堆(Register file,一個 CPU 中的一組寄存器)

相比之下,亂序處理器(Out-of-order processors)處理指令通常有以下幾步:

  1. 指令獲取
  2. 指令被分發到指令隊列
  3. 指令在指令隊列中等待,直到輸入操作對象可用(一旦輸入操作對象可用,指令就可以離開隊列,即便更早的指令未被執行)
  4. 指令被分配到適當的功能單元並執行
  5. 執行結果被放入隊列(而不立即寫入寄存器堆)
  6. 只有所有更早請求執行的指令的執行結果被寫入寄存器堆後,指令執行的結果才被寫入寄存器堆(執行結果重排序,讓執行看起來是有序的)

從上面的執行過程可以看出,亂序執行相比有序執行能夠避免等待不可用的操作對象(有序執行的第二步)從而提高了效率。現代的機器上,處理器運行的速度比記憶體快很多,有序處理器花在等待可用數據的時間里已經可以處理大量指令了。
現在思考一下亂序處理器處理指令的過程,我們能得到幾個結論:

  1. 對於單個 CPU 指令獲取是有序的(通過隊列實現)
  2. 對於單個 CPU 指令執行結果也是有序返回寄存器堆的(通過隊列實現)

由此可知,在單 CPU 上,不考慮編譯器優化導致亂序的前提下,多線程執行不存在記憶體亂序訪問的問題。我們從內核源碼也可以得到類似的結論(代碼不完全的摘錄):


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 如題 報錯提示: 使用 JSON JavaScriptSerializer 進行序列化或反序列化時出錯。字元串的長度超過了為 maxJsonLength 屬性設置的值。","StackTrace 解決方案 在web.config 中configuration節點 插入 ...
  • 一、Kali簡介 1.1、相關連接 Kali百度百科:https://baike.baidu.com/item/Kali%20linux/8305689?fr=aladdin Kali wiki:https://en.wikipedia.org/wiki/Kali Kali官網:https://ww ...
  • 本章內容 系統安裝過程 配置anaconda 自動化安裝系統 製作引導光碟和U盤 DHCP服務 PXE安裝系統 cobbler企業級應用 安裝程式 • CentOS系統安裝 系統啟動流程: bootloader-->kernel(initramfs)-->rootfs-->/sbin/init • ...
  • 1. 修改drivers/input/touchscreen/下的Makefile 2. 修改.config,找到CONFIG_TOUCHSCREEN_S3C2410,修改為 3. make uImage 4. 下載新內核,啟動開發板,cd /mnt 5. cd ts_dir 6. cp * / - ...
  • 環境交代 ========== Linux系統:CentOS7, Xshell版本:6 操作步驟 下麵我們以一個文件上傳來演示用法 第一步 建立連接,這裡不多說 在Xshell中點擊如下圖標,或者直接按 Alt+Ctrl+F來新建文件傳輸 出現彈窗直接關閉即可 然後會出現以下界面 輸入help 查看 ...
  • 1.終端執行安裝命令 2.如下圖,多出Input method 3.點擊進行配置 4.reboot重啟系統,新建一個文本,試一下輸入,問題出來(難道是當時新建虛擬機時沒有選擇增強型虛擬鍵盤) ...
  • 1. 寫好觸摸屏驅動後,安裝tslib 1.1 tar xzf tslib-1.4.tar.gz 1.2 cd tslib 1.3 修改編譯器版本號或者內核版本號,使它們一致。不然會出錯,顯示selected device is not a touchscreen I understand 在內核中 ...
  • 192.168.4.119 為本機的ip地址;每條鏈的規則是由上至下進行匹配,因此我們需要把範圍小的規則放在上面以防被覆蓋。 1)清空iptables預設規則,並自定義規則 2)多埠匹配,開放21,22,23,80,3306埠 3)連續地址的匹配,允許192.168.4.1 ~ 192.168. ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...