在計算機體系中,數據並行有兩種實現路徑:MIMD(Multiple Instruction Multiple Data,多指令流多數據流)和SIMD(Single Instruction Multiple Data,單指令流多數據流)。其中MIMD的表現形式主要有多發射、多線程、多核心,在當代設計的以處理能力爲目標驅動的處理器中,均能看到它們的身影。同時,隨着多媒體、大數據、人工智能等應用的興起,爲處理器賦予SIMD處理能力變得愈發重要,由於這些應用存在大量細粒度、同質、獨立的數據操做,而SIMD天生就適合處理這些操做。程序員
SIMD結構有三種變體:向量體系結構、多媒體SIMD指令集擴展和圖形處理單元。本文集中圍繞向量體系結構進行描述。算法
向量體系結構的基本組成
1.簡介數組
向量體系結構使用一條向量指令開啓一組數據操做,其中數據的加載、存儲以及數據計算以流水線的形式進行。它最大的特色就是:其僅在一組數據操做的第一個元素存在存儲器延遲和由冒險引發的停頓,後續元素會沿着流水線順暢流動。性能優化
咱們經過一個以cray-1爲基礎的向量處理器來分析。它同時包含標量體系結構和向量體系結構,其標量指令集爲MIPS,向量指令集則稱爲VMIPS。微信
2.架構組成多線程
VMIPS指令集體系結構的主要組件以下圖所示:架構
圖1 向量體系結構VMIPS的基本結構性能
向量寄存器:N個向量寄存器,每一個向量寄存器都是一個固定長度的寄存器組,用於保存一個向量。向量長度一般爲3二、64等,這也是單條向量指令所能執行元素的最大值。大數據
標量寄存器:用於給向量功能單元提供標量輸入數據、給載入/存儲單元提供地址,或是做爲向量長度寄存器、向量遮罩寄存器等功能。優化
向量功能單元:每一個單元都徹底實現流水化,能夠在每一個時鐘週期開啓一個新的操做。每一個功能單元可能只有單條流水線(單車道),也能夠有多個並行的流水線(多車道)。
向量載入/存儲單元:向量載入與存儲操做也是徹底流水化的,在初始延遲後,能夠以每一個時鐘週期一個只的帶寬移動字。該單元一般還會處理標量載入和存儲。
3.向量長度寄存器
向量處理器有一個天然向量長度,由向量寄存器的長度決定。然而在實際程序中,特定向量運算的長度在編譯時一般是未知的。例如如下代碼:
for (i=0; i<n; i++)
{
y[i] = a * x[i] + y[i];
}
向量運算的長度取決於n,而n是個變量,它可能在執行時隨應用狀況而變化。
向量長度寄存器(VLR)就是爲了解決實際數據向量長度變化這一問題,它控制一條向量指令執行元素的長度,但這一長度不能超過天然向量長度。
4.向量遮罩寄存器
考慮以下循環:
for (i=0; i<64; i++)
{
if (y[i] != 0)
{
y[i] = x[i] - y[i];
}
}
因爲循環體須要條件執行,所以不可能對一組數據向量的元素執行相同操做,也就不便於對循環進行向量化。
經過啓用向量遮罩寄存器能夠解決這一問題。它能夠經過布爾向量來控制一條向量指令中每一個元素運算的條件執行,如向量中某元素對應的向量遮罩寄存器中的布爾值爲1,則執行指令操做,不然執行空操做。
須要注意的一點是,即便遮罩爲0的元素,它仍會佔用與遮罩爲1元素相同的執行時間。
較標量處理器的優點
向量體系結構與標量體系結構相比,其優點有:
僅在每一個向量載入或存儲操做中付出較長的存儲器延遲時間,而不須要在載入或存儲每一個元素時耗費時間。
減小了流水線互鎖的頻率。多個指令間可能因爲相關性而引發停頓,向量體系結構中每條向量指令僅需一次流水線停頓,而不是每一個向量元素操做須要一次。
大幅縮減了動態指令帶寬。向量體系結構使本來在標量體系中每一個元素操做須要一條指令,變成了每一個向量操做僅須要一條向量指令。
向量體系結構 & 軟件流水線
在看向量體系結構的指令處理過程時,很容易將它與軟件流水線的概念混淆在一塊兒。實際上兩者確實有不少類似之處,咱們能夠經過一個例子來分析:
for(i=0 ; i<16; i++)
{
sum += tab[i];
}
如上的一個循環體,實現16長度的數組求和。假設處理器中含有並行的加載/存儲單元(.D)與算術單元(.L),.D單元執行一條指令消耗4個時鐘週期,.L單元執行一條指令消耗1個時鐘週期。
在標量處理器中不進行軟件流水線編排,假設不考慮循環操做指令的開銷,其在不一樣時刻執行指令的過程大體以下所示:
clock .D .L
1 load1
2
3
4
5 load2 add1
6
7
8
9 load3 add2
……
61 load16 add15
62
63
64
65 add16 (結束)
在標量處理器中進行指令軟件流水線編排後,在不一樣時刻執行指令的過程大體以下所示:
clock .D .L
1 load1
2 load2
3 load3
4 load4
5 load5 add1
6 load6 add2
7 load7 add3
8 load8 add4
10 load9 add5
11 load10 add6
12 load11 add7
13 load12 add8
14 load13 add9
15 load14 add10
16 load15 add11
17 load16 add12
18 add13
19 add14
20 add15
21 add16 (結束)
在向量處理器中,假設向量長度爲16,功能單元僅有單車道,在不一樣時刻執行指令的過程大體以下所示:
clock .D .L
1 vload
2
3
4
5 vadd
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 (結束)
從以上的例子中能夠很清楚地看出:
軟件流水線和向量指令具備相同的時鐘消耗,它們都僅在處理第一個元素時才發生延遲停頓。
軟件流水線並無減小指令的個數,而向量指令處理一組向量操做僅需一條向量指令。
總結起來就是:軟件流水線能夠減小流水線停頓,但很難大幅縮減指令帶寬要求;而向量體系結構既能減小流水線停頓,也能大幅縮減指令帶寬要求。
也有現代處理器專門針對軟件流水線而設計硬件和指令支持。例如TI C6000架構中的SLOOP BUFFER單元和軟件流水指令,能夠將上述軟件流水線例子中clock5~clock17的指令表示成一條load和add指令,稱爲軟件流水核。這樣的處理方式也實現了縮減指令帶寬的目的。
所以,宏觀上能夠認爲:向量體系結構是經過指令集的設計支持,在硬件上實現可向量化循環的流水化操做;軟件流水線是經過編譯器端的指令編排(或是加上部分硬件設計支持),在軟件上實現可向量化循環的流水化操做。
一些現實問題
1.處理非整數倍天然向量長度的向量
在實際程序中,特定向量運算的長度一般都是未知的,好比如下代碼:
for (i=0; i<n; i++)
{
y[i] = a * x[i] + y[i];
}
假設向量體系結構的天然向量長度MVL=64,當n=64k(1,2……),咱們能夠很方便的把代碼寫成一個向量指令的k次循環。但更有可能的是,n不爲64的整數倍,爲了應對各類不一樣的狀況,該怎麼組織代碼呢?
一種名爲條帶挖掘(strip mining)的技術能解決這一問題。條帶挖掘是指生成一些代碼,使每一個向量運算都是針對小於或等於MVL的大小來完成的。事實上任何n均可以差分紅MVL的整數倍部分和餘數部分,因而咱們能夠建立兩個循環,分別處理這兩個部分。向量體系結構利用向量長度寄存器,在編譯時能夠生成一個條帶挖掘循環。
處理上面例子的條帶挖掘C語言的寫法以下:
low = 0;
VL = (n % MVL)
for(j=0; j<=(n/MVL); j++)
{
for(i=low; i<(low+VL); i++)
{
y[i] = a * x[i] + y[i];
}
low += VL;
VL = MVL;
}
如此即可將整個向量分段,第一段的長度爲餘數部分(n%MVL),後續段長度爲MVL。
2.提供充足的內存帶寬:內存組(memory bank)
如下幾個緣由形成向量處理器對存儲帶寬需求很大:
許多向量處理器單時鐘週期能夠產生多個內存訪問操做
可能存在多個向量處理器同時獨立訪問同一存儲器系統
存儲器組的週期時間一般比處理器週期時間高几倍
多數向量處理器支持非連續數據字的訪問
對於存儲器組而言,一般同一時刻對同一存儲器組只能有一個訪問存在,上面這些因素使得處理器架構師須要設置大量的獨立存儲器組,以知足同一時刻對多個存儲器組的訪問。
3.非單位步幅存儲訪問
當要訪問的一組向量數據在內存中的位置並非連續的,稱這時元素的訪問爲非單位步幅。現實中有不少這樣的例子,例如對矩陣的訪問:
for(i=0; i<m ; i++)
{
C[i] = 0;
for(j=0; j<n; j++)
{
C[i] += A[i][j] * B[j][i];
}
}
因爲在C語言中行元素是依次排列的,所以對矩陣A的訪問就是單位步幅的,而對矩陣B的訪問是非單位步幅的(設j>1)。
向量處理器的主要優點之一就是可以訪問非連續存儲器位置,並對其進行調整,放到一個密集結構中。不過,爲支持大於1的步幅,會使存儲器系統變得複雜。引入非單位步幅後,就有可能頻繁訪問同一個組,引發存儲器組衝突,從而使某個訪問停頓。
舉個存儲器組衝突的例子:假定有16個存儲器組,組繁忙時間爲6個時鐘週期,每一個時鐘週期以步幅deta訪問存儲器的一個字(4字節):
Bank | 1 | 2 | 3 |...
Address | 0 1 2 3 | 4 5 6 7 | 8 9 10 11 |...
Address | 64 65 66 67 | 68 69 70 71 | 72 73 74 75 |...
若deta=1,則永遠不會發生存儲器組衝突;若deta=4,則第一次訪問將與第五次訪問發生衝突……
在《計算機體系結構.量化方法研究》這本書中,做者給出了一個判斷是否會產生存儲器組衝突的公式,可能因爲筆誤發生了一點小錯誤,原書表達以下:
若知足如下條件,則會產生組衝突,從而產生停頓:
(組數/步幅與組數的最小公倍數) < 組繁忙時間
實際上用這個公式去套上面的例子,顯然是講不通的。一開始我覺得是翻譯錯誤,而後去看了英文本來,書上是這麼寫的:
翻譯並無問題。我本身推導後,認爲正確的表示應該是這樣的:
若知足如下條件,則會產生組衝突,從而產生停頓:
(步幅與組數的最小公倍數/步幅) < 組繁忙時間
或者
(組數/步幅與組數的最小公約數) < 組繁忙時間
如下是推導過程,若有疏忽,請指正:
4.集中-分散存儲訪問
稀疏矩陣在應用中是很常見的,在稀疏矩陣中,向量的元素一般以某種緊湊形式存儲,而後對其進行間接訪問。咱們可能會看到相似下面的代碼:
for(i=0; i<n i++)
{
A[k[i]] = A[k[i]] + C[m[i]];
}
假設索引數組k和m的內容是不連續的,則對矩陣A和C的加載稱爲集中操做;對矩陣A的存儲操做稱爲分散操做。對此類操做的支持被稱爲集中-分散(gather-scatter)。
幾乎全部向量處理器都具有這一功能,這一技術容許以向量模式運行帶有稀疏矩陣的代碼。因爲可能不知道k和m的值是離散的,簡單的向量化編譯器可能沒法自動實現以上源碼的向量化,需程序員給編譯器發出提示。
參考資料
【1】John L. Hennessy,David A. Patterson . 計算機體系結構:量化研究方法:第5版[M].北京:人民郵電出版社,2013. (原名《Computer Architecture:A Quantitative Approach》)
·END·
歡迎來個人微信公衆號作客:信號君
專一於信號處理知識、高性能計算、現代處理器&計算機體系
技術成長 | 讀書筆記 | 認知升級
幸會~
你可能還感興趣: