什么是SIMD
SIMD的全稱是Single Instruction Multiple Data (單指令多數(shù)據(jù)流)队橙。
在支持SIMD的CPU中拦坠,包含著一些特別寬的寄存器(比如512位)连躏。通過特別的指令,可以在這些寄存器上執(zhí)行指定操作贞滨。這些操作通常是對正常寄存器(比如64位)上操作的拓展入热,可以理解為一條指令同時操作了多個正常寄存器,也就是所謂的SIMD了晓铆。
SIMD的性能
做個簡單的除法就能知道勺良,512位的寄存器相比64位寄存器,速度提升了8倍骄噪。
但是實際情況不僅僅是如此尚困。在SIMD的指令中,還包括了一些非常奇妙的指令链蕊,比如計算正態(tài)分布的累積分布函數(shù)和其反函數(shù)的指令事甜。在看到它們的時候谬泌,我心里吼了一句:“還有這種操作!”逻谦。這些特化的指令在特別的場景下就是神器掌实。
怎么用SIMD
首先,我們是在Linux的GCC編譯器上使用SIMD指令跨跨。在這個條件下潮峦,有兩個途徑:
- 嵌入式匯編
- Intrinsics
嵌入式匯編不是今天的主題。我今天主要記錄一下Intrinsics怎么用勇婴。不管使用哪種方法忱嘹,有一個網(wǎng)站是一定要收藏的:Intel Intrinsics Guide
它給出了SIMD指令集的各個子集: MMX, SSE耕渴,SSE4.2拘悦,AVX2等等。同時橱脸,它給每個指令都打上一些標簽用于檢索:Load础米,Store,Cast添诉,Arithmetic 等等屁桑。它還給出了每個指令的等價操作和匯編指令。
具體地說栏赴,在C語言中使用SIMD涉及三個方面:
- 頭文件
- 函數(shù)調(diào)用
- 編譯選項
頭文件和函數(shù)調(diào)用很好辦蘑斧,它歸屬于Intel的規(guī)范。在Intel Intrinsics Guide中须眷,每條指令需要的頭文件都有標注竖瘾,按圖索驥即可。
編譯選項則屬于GCC的規(guī)范花颗。 i386 and x86-64 Options 將相關選項包含在內(nèi)捕传,但是更寬一些。每條指令都有所屬的指令集(比如SSE4.2)扩劝,當使用到該指令后庸论,就要在鏈接器的選項中加上相關的項 (比如-msse4.2
) 。
選項的命名很直接棒呛,在 i386 and x86-64 Options 里搜索 -mmmx
就可以跳到SIDM選項比較集中的區(qū)域葡公,很容易就能確定需要的選項是什么。
內(nèi)存對齊
使用SIMD指令的范式很簡單:
- 用SIMD指令条霜,將數(shù)據(jù)從內(nèi)存導入特殊寄存器
- 用SIMD指令,在特殊寄存器間進行運算
- 用SIMD指令涵亏,將運算結(jié)果導出回內(nèi)存
這里涉及到一個問題宰睡,就是導入導出使用到的內(nèi)存必須滿足特殊的對齊條件蒲凶。比如使用了128位(16字節(jié))的SIMD,則內(nèi)存首地址必須能被16整除拆内。如果不滿足該條件旋圆,在導入數(shù)據(jù)時程序會引發(fā)段錯誤退出。
在C中麸恍,獲得特定對齊方式的動態(tài)內(nèi)存灵巧,使用的函數(shù)是來自stdlib.h
的void* aligned_alloc(size_t alignment, size_t size)
。
使用案例
// Filename: main.cpp
#include <cstdlib>
#include <cstdio>
#include <immintrin.h>
using namespace std;
void print(float* data, int n) {
for (int i = 0; i < n; i ++) {
printf("%f ", data[i]);
}
printf("\n");
}
int main() {
const int WIDTH = 256;
const float x = 0.2;
int n = WIDTH/8/sizeof(float);
float* w = (float*) aligned_alloc(64, sizeof(float)*n);
float* y = (float*) aligned_alloc(64, sizeof(float)*n);
// 生成數(shù)據(jù)
for(int i = 0; i < n; i ++) {
w[i] = rand();
}
print(w, n);
// y_i = w_i * x
__m256 _x = _mm256_set1_ps(0.2);
__m256 _w = _mm256_load_ps(w);
__m256 _y = _mm256_mul_ps(_x, _w);
_mm256_store_ps(y, _y);
print(y, n);
free(y);
free(w);
}
編譯的指令如下:
g++ -o a.out -mavx main.cpp