一個系列:之一
外面的世界總是充滿了危險性,因此在開始探險之前應該做好充分的準備。即使有些準備怎么看來也是多余的,但在關鍵時刻卻能發揮很大的用途
. 我現在即將去冒險,這途中可能會遇到很多有趣的事情,收獲
,也會接到
。不過這都無所謂,是我為成功的準備(匹克運動鞋廣告?!)。
控制論中,線性行為是最簡單的。而我發現世界喜歡簡單的東西于是有了我喜歡線性系統的說法。事情在CPU的世界中本來也是這樣進行的。可是有一天,我們發現線性是“低效”的(請注意這個引號)。為什么呢?因為指令總是必須搭乘固定時刻的車走,如果恰巧車來的時候排在前面的人不上,則會浪費一到幾趟車的時間(當然,這個比喻并不恰當),于是我想,我是不是可以加塞兒?雖然這可能得到眾人的強烈鄙視,但是卻可以最大限度的利用這有限的資源。于是線性的世界被打破了。隊列中出現了不可預測的加塞兒現象,稱為“非順序執行(Out-of-order execution)”。眾多的學者在上世紀70到80年代在這個問題上進行了眾多的研究。大家都知道,研究人員喜歡作定義以便交流,于是我們也做個定義。
非順序執行:英文稱作 Out-of-order execution,也稱亂序執行。是一種在高性能CPU設計中所廣泛采用的技術。這個技術企圖最大限度的利用CPU的各個周期,改變指令的執行順序,以降低慢操作或其他操作造成的延遲。這里所說的改變執行順序,是CPU的行為,不是編譯器的行為!
支持非順序執行的處理器出現的很早(1960)但是我們耳熟能詳的卻是在1995年出現的一代“劃時代的與垃圾的”處理器(Intel Pentium Pro),與AMD K5處理器(1996)。此后,亂序執行被一代代高性能處理器視為必須擁有的功能而流傳至今(如果你對亂序執行的算法感興趣,可以看看較早的亂序算法--Tomasulo算法。)
與現實世界中讓人厭煩的加塞兒不同,處理器中的亂序執行讓人看起來好像就像順序執行一樣,邏輯并沒有發生絲毫改變。于是我們為這個偉大的發現歡欣鼓舞了很長時間。但是有一天,聰明的程序員試圖用多線程(可能一開始是為了解決用戶界面阻塞的問題)時卻發生了令人困擾的錯誤。結果,困擾的原因是我們第一句話漏了三個字:單線程。實際上,CPU的亂序執行能力保證在單線程環境下改變執行的邏輯。例如以下的代碼片段(我們的默認語言是C#因為這是一個.NET博客
):
(我們認為,測試環境是在 IA-32 平臺或者 IA-64 平臺之下,在.NET環境下)
2 volatile int a = 1;
3 // 中間可能有其他代碼
4 x = 2; // st.rel
5 while (y == 1); // ld
6 a = y; // ld.acq
問題是,以上的代碼果真會按如程序所示的順序執行么?實際上可能是以下的順序。
2 a = y; // ld.acq
3 while (y == 1); // ld
但是從單線程的邏輯上來看,兩種執行順序完成后都是同樣的一個效果。但是在多線程環境下,由于執行順序的改變就可能造成問題了!!
慢!可能現在已經要暈了。你最關心的可能并不是后果是什么,而是關心這種順序的改變是誰造成的,什么時候造成的。我們首先明確一下順序的概念。順序有三種:
(1)程序順序:指在特定CPU上運行的,執行內存操作的代碼的順序。
(2)執行順序:指在CPU上執行的獨立的內存相關的代碼執行的順序。執行順序和程序順序可能不同,這種不同是編譯器和CPU優化造成的結果。我們上述所謂的執行亂序就是指執行順序中,由CPU帶來的執行順序的改變。
(3)感知順序:指特定的CPU感知到他自身的或者其他CPU對內存進行操作的順序。感知順序和執行順序可能還不一樣。這是由于緩存優化或者內存優化系統造成的。
用白話說就是這個樣子的:
話說你某一天編寫了一段代碼-->你在編譯器上進行編譯發現很順利(但是在編譯的過程中可能你的代碼順序已經被改變了,這種改變應該屬于執行順序改變)-->你試圖運行目標代碼(目標文件中的代碼的順序應該是程序順序)-->你的代碼開始被執行(但是執行過程中CPU對內存操作進行了亂序,這屬于執行順序的改變,也是我們上述例子不斷在說的事情)-->你的部分代碼執行完畢之后各個CPU對執行結果的感知的順序可能也是不同的(例如,CPU 1操作了內存單元1進而操作了內存單元2,但是另一個CPU先看到了內存單元2的改變而后又看到了內存單元1的改變)。
結論:我們探討的問題是“執行順序”中由CPU改變的那一部分。
好的,在我們繼續討論之前我們可以探討一下,我們的Daily Work中有哪些行為可能會對上述的各種順序造成影響呢?列個不完全清單(注意!我們討論的模型還是在.NET內存模型下):
(1)當你使用 volatile 的時候的時候,你阻止了編譯器對相應內存代碼的優化。阻止了大部分的編譯器造成的執行順序的改變。但是你根本不能夠阻止CPU對執行順序的改變(除非處理器有意如此,例如IA-64處理器會在volatile訪問時安插Memory Fence)!因此Intel技術博客上有文章指出:“volatile 對于多線程編程幾乎沒有任何用處”。
(2)當你使用Memory Barrier或者Memory Fence的時候你從某種程度上(取決于你使用了哪一種)阻止了CPU和編譯器對執行順序的改變。
好了,這就是第一篇。目的是說明:在上路之前還有好多準備工作要做。下一篇就具體針對.NET環境說說如何應對Out of order execution。
非順序執行:英文稱作 Out-of-order execution,也稱亂序執行。是一種在高性能CPU設計中所廣泛采用的技術。這個技術企圖最大限度的利用CPU的各個周期,改變指令的執行順序,以降低慢操作或其他操作造成的延遲。這里所說的改變執行順序,是CPU的行為,不是編譯器的行為!
浙公網安備 33010602011771號