从编译到执行,C++如何开发SIMD友好的代码?
一:名詞解釋
Flynn分類法
Flynn于1972年提出了計算平臺的Flynn分類法,主要根據指令流和數據流來分類。按照Flynn分類法,計算平臺共分為四種類型。
1.單指令流單數據流機器(SISD)
?
2.單指令流多數據流機器(SIMD)
?
3.多指令流單數據流機器(MISD)
?
4.多指令流多數據流機器(MIMD)
?
標量處理器
標量處理器是一種最簡單的計算機處理器類型。這類處理器在同一時間內只處理一條數據(整數或浮點數)。標量處理器是一種單指令流單數據流(SISD)處理器。
向量處理器
當今大多數商業 CPU 都包括一些向量處理器指令,較為典型的是 SIMD。從運算上說,標量機只是一個數一個數地進行計算,而向量機則能夠對一批數據同時進行加工處理。因此,向量機比標量機的運算速度快,更適合于演算數據量多的大型科學、工程計算問題。
并行
不同于并發的概念,并發偏重于多個任務交替執行,而多個任務之間有可能還是串行。真正意義上的“同時執行”。同時做很多事情。
SIMD
讀做(sim-dee),單指令流多數據流(英語:Single Instruction Multiple Data,縮寫:SIMD)是一種采用一個控制器來控制多個處理器,同時對一組數據(又稱“數據向量”)中的每一個分別執行相同的操作從而實現空間上的并行性的技術。實現這種架構的并行處理機,每次都執行相同的指令,對不同的數據進行處理。這種計算機適合處理矩陣計算。
keypoints:
1.SIMD結構的CPU有多個執行部件,但都在同一個指令部件的控制之下,中央控制器向各個處理單元發送指令,整個系統只要求有一個中央控制器,只要求存儲一份程序,所有的計算都是同步的。
2.以加法指令為例,單指令流單數據流(SISD)型CPU對加法指令譯碼后,執行部件先訪問主存,取得第一個操作數,之后再一次訪問主存,取得第二個操作數,隨后才能進行求和運算;而在SIMD型CPU中,指令譯碼后,幾個執行部件同時訪問主存,一次性獲得所有操作數進行運算。
3.SIMD不是一種具體的指令格式,經歷了mmx->sse->avx這樣的發展趨勢,但它們都屬于SIMD,SIMD更像是一種滿足某種規范的技術架構總稱。
SSE
Intel在1999年引入了SSE指令,SSE是"Streaming SIMD Extention"(流SIMD擴展)的縮寫,SSE是Streaming SIMD Extensions(流SIMD擴展)的縮寫。是由 Intel公司,在1999年推出Pentium III處理器時,同時推出的新指令集。SSE是一種SIMD指令集。SSE有8個128位寄存器,XMM0 ~XMM7。這些128位元的寄存器,可以用來存放四個32位的單精確度浮點數。SSE的浮點數運算指令就是使用這些寄存器。
keypoints:
-Intel的初代SIMD指令集是MMX,Multi-Media Extension, 即多媒體擴展,因為它的首要目標是為了支持MPEG視頻解碼。后來Intel進一步實現了SSE, SSE2~SSE4指令集,給了他們單獨的寄存器,之后MMX就停掉了。
-SSE包含70條新的指令,其中大部分指令用于單精度 浮點數據。當對多個數據對象執行完全相同的操作時,SIMD指令可以大大提高性能。
-SSE隨后被英特爾擴展為SSE2,SSE3,SSSE3和SSE4。-SSE功能歷經幾代,最新的版本為AVX(advanced vector extension)
-SSE中大部分指令要求地址是16byte對齊的。-要能夠使用 Intel 的 SIMD 指令集,不僅需要當前 Intel 處理器的硬件支持,還需要編譯器的支持。
-由于SIMD指令有多個版本,每個版本支持的指令集不同。所以如果你的軟件要支持更多的CPU,就要在使用SIMD指令之前知道當前指令運行所在的CPU是否支持這條指令。
-SSE發展到SSE5戛然而止。這里有故事,2007年8月,AMD搶先宣布了SSE5指令集(之前從SSE到SSE4均為Intel制定),Intel火大,轉而另起爐灶,2008年4月,Intel公布了AVX指令集規范,隨后開始不斷進行更新。
?
二:C++如何開發SIMD友好的代碼?
使用SIMD技術的開發方法業內有如下幾種:
1.使用著名的IPP庫,IPP的全稱是Intel Integrated Performance Primitives
2.方法是使用編譯器的自動向量化(Auto-vectorization)支持。
3.使用編譯器指示符(compiler directive),如使用英特爾的C/C++編譯器(ICC)編譯如下代碼,那么ICC便會對#pragma simd指示符下面的for循環做向量化
4.使用Cilk技術。
5.使用編譯器的內建函數(intrinsic),例如要使用要使用SSE3,#include,如果不關心使用那個版本的SSE指令,則可以包含所有#include
6.直接使用匯編語言編寫匯編函數,C++調用匯編函數
?
編寫高效程序需要做到以下幾點:
第一,必須選擇一組適當的算法和數據結構。
第二,必須編寫出編譯器能夠有效優化以轉換成高效可執行代碼的源代碼。
參考:http://sci.tuomastonteri.fi/programming/sse
三:從編譯到執行,發生了什么事情?
我們剛才提到了C++代碼,也提到了指令集。
提到了如何寫SIMD友好的C++代碼和SSE指令有多神奇。
可是,這一切是怎么發生的呢?
從C++源代碼編譯生成可執行代碼,到CPU運行load到內存的這些代碼。
這中間,發生了什么事情?
我們來聊聊CPU指令集和GCC編譯的那點兒事兒。
先來溫習一下計算機體系結構的基礎知識:
指令 = 操作碼 + 操作數
操作碼:表示指令要完成的工作,如存數,取數。
操作數:操作對象的內容或者所在的單元格地址。
計算機工作的過程實際上是快速的執行指令的過程,當計算機在工作時,有兩種信息流在流動。一種是數據流,通常是各種原始數據、中間結果等。一種是控制流,是由各種控制指令構成的。
指令的執行過程:
1.取指令,從內存儲器取出指令到指令寄存器。
2.分析指令,對指令寄存器中存放的指令進行分析,由譯碼器對操作碼進行譯碼。將指令的操作碼轉換成相應的控制電信號,并由地址碼確定操作數的地址。
3.執行指令。由操作控制線路發出的完成該操作所需的一系列控制信息。用以完成該指令所需的操作。
4.為執行下一條指令做準備。形成下一條指令的地址。指令計數器指向存放下一條指令的地址,最后控制單元將執行結果寫入內存。
上述完成一條指令的執行過程叫做一個“機器周期”;計算機在運行時,CPU從內存讀取一條指令到CPU內執行,指令執行完,再從內存讀取下一條指令到CPU執行。CPU不斷的取指令、分析指令、執行指令,再取下一條指令,這就是程序的執行過程。
再看高級語言編譯過程,是將源代碼轉換為機器可認識代碼的過程。編譯程序讀取源程序(字符流),對之進行詞法和語法的分析,將高級語言指令轉換為功能等效的匯編代碼,再由匯編程序轉換為機器語言,并且按照操作系統對可執行文件格式的要求鏈接生成可執行程序。
C/C++源代碼編譯成相應平臺下的可執行文件需要經過如下步驟:
1、預處理
2、編譯
3、匯編
4、鏈接
接下來我們探索一下C++編譯的機器碼如何和SIMD掛上了鉤的?
首先,需要被執行的機器碼先要被OS調度到內存之中, 程序執行時, 機器碼依次經過了Memory--Cache--CPU fetch, 進入CPU流水線, 接著就要對它進行譯碼了, 譯碼工作生成的是CPU內部數據格式, 微碼。特別需要注意的是:CPU不需要任何形式的存儲介質去存儲指令集, 因為"譯碼"這個步驟就是在對指令集里規范的機器碼做解碼。具體的指令編程機器碼后就會變成數字電路的開關信號。其中某幾段會作為控制信號,控制其他部分的數據走不同的電路以執行運算。
關鍵點就在這里了,從匯編到機器碼這步是匯編程序翻譯的。匯編程序知道某條指令要翻譯成什么樣的機器碼。匯編的前提是一定要有格式,支持什么指令, 指令帶什么限制條件, 用什么操作數, 用什么地址, 其實都是指令集規范的內容, 如果寫錯了, 就無法翻譯成機器碼。
比如C++編譯后的匯編文件中某條支持SSE的某條匯編語句:
movaps? %xmm1, %xmm4
翻譯成如下的機器碼:
26 66 c7 84 c8 44 33 22 11 78 56
到真正執行的層面,CPU將指令(一個由0,1構成的字符串)輸入譯碼電路,譯碼電路根據指令集的描述,生成各種控制信號。
控制整體晶體管邏輯電路開始工作。這個機器碼就是SIMD的機器碼,在單個時鐘周期里能夠并行的從內存中讀取和計算數據。
FAQ:
1.ClickHouse怎樣用到SIMD技術?
目前Clickhouse通過編譯器的內建函數(intrinsic)開發的方式來利用到SIMD技術。
2. ClickHouse為什么不用更牛逼的MIMD ?
MIMD和SIMD只是體系結構的兩種概念,并不是說采用SIMD支持的指令集就無法使用MIMD操作。實現SIMD概念的SSE3(舉例),就支持某些MIMD操作。另外,目前筆者也沒有發現有C++能夠支持MIMD機器的優化。
注:由于每個執行單元的指令流都是相同的,SIMD模式將指令的獲取時間均攤到每一個執行單元。但是,當指令流出現分支,指令就會被序列化。而MIMD模式的設計主要是為了處理不同指令流,當指令流出現分支,它不需要對線程進行阻塞。然而它需要更多指令存儲以及譯碼單元,這就意味著硬件需要更多的硅,同時,為了維持多個單獨的指令序列,它對指令帶寬的需求也非常的高。一般使用SIMD與MIMD的混合模式才是最好的方案。用MIMD的模式處理控制流,用SIMD的模式處理大數據,在CPU上使用SSE/MME/AVX指令擴展集時就是采用的SIMD與MIMD的混合模式
3.如何編寫可以自動矢量化的代碼?
參考:http://www.jianshu.com/p/186339c16e8c
這個例子說明,現在處理器具備相當的計算能力,但是我們需要按照特定程式化的方式來編寫程序,就可以將這些能力誘發出來。
題外話:
C++語言之父Bjarne在“2016 C++及系統軟件技術大會”上。做了題為《What C++ is and what it will become》的演講。
what is C++ --
他用兩行話描述了C++是什么:
1.“Direct map to hardware” -- 直接映射到硬件
2.“Zero overhead abstraction” -- 0負擔抽象
對于1,C++的字節對齊以及多指令集的支持,使之能夠直接翻譯對應到CPU的指令。C++的數據類型也可以直接對應到CPU支持的數據類型。無需進行轉換。可謂效率奇高。
對于2,今天的JAVA、C#都有抽象,但有些語言為了抽象付出的代價很大,資源消耗多,運行速度慢,C++基本0負擔,無額外開銷。
what it will become --
提到了10項內容,第8項提到了在語言層面支持SIMD向量和并行化算法。
期待語言層面的SIMD支持和并行算法封裝。這樣,在編寫能夠支持SIMD優化代碼時,可能就不用關注太多的細節了。
作者:_金科
鏈接:https://www.jianshu.com/p/6b1bbbefbb70
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的从编译到执行,C++如何开发SIMD友好的代码?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Jenkins Ci系列目录
- 下一篇: 迄今最大最详细的银河系地图发布!涵盖20