向量化程式碼實踐與思考:如何藉助向量化技術給程式碼提速
來源:阿里雲開發者
阿里妹導讀
一、計算加速的技術
void squre( float* ptr ){ for( int i = 0; i < 4; i++ ) { const float f = ptr[ i ]; ptr[ i ] = f * f; }}
void squre(float * ptr){ __m128 f = _mm_loadu_ps( ptr ); f = _mm_mul_ps( f, f ); _mm_storeu_ps( ptr, f );}
二、SIMD擴充套件指令集
指令集需要CPU硬體支援,下面列出了支援各個指令集的CPU。
ARM也引入了SIMD擴充套件指令。典型的SIMD操作包括算術運算(+-*/)以及abs、sqrt等,完整的指令集合請參考英特爾提供的使用文件:
編譯器自動向量化
靜態編譯 即時編譯(JIT)
三、編譯器靜態自動向量化
1、程式碼滿足一定的正規化,後續會詳細展開介紹各種case;
3.1 編譯器選擇和選項
只有高版本的編譯器才能實現向量化,gcc 4.9.2及以下經測試不支援向量化,gcc 9.2.1支援。gcc對向量化的支援更加友好,clang對某些程式碼無法轉化成向量化,而在某些情況下,clang生成的向量化程式碼效能比gcc更好(採用更寬的暫存器指令導致的),不一而足。因此,建議編寫符合規範的程式碼,然後分別測試兩種編譯器的效能。
res[i] = tmpBitPtr[i] & opBitPtr[i]; //使用下標訪問地址,clang和gcc都支援*(res + i) = *(tmpBitPtr + i) & *(opBitPtr + i); //使用地址運算訪問記憶體,clang不支援,gcc支援
四、如何寫出可向量化的程式碼
迴圈的變數初始值和結束值要固定,例如:
for (int i = 0;i < n ;++i ) //總的次數是可以計數的,這種寫法可以向量化for (int i = 0;i != n;++i) //總的次數不可計數,這種寫法無法向量
for (int i=0; i<SIZE; i+=2) b[i] += a[i] * x[i]; //訪問連續空間,可以向量化for (int i=0; i<SIZE; i+=2) b[i] += a[i] * x[index[i]] //訪問非連續空間,不能向量化
資料依賴有幾種場景:
for (j=1; j<MAX; j++) A[j]=A[j-1]+1;// case 1 先寫後讀,不能向量化for (j=1; j<MAX; j++) A[j-1]=A[j]+1;// case 2 先讀後寫,不能向量化for (j=1; j<MAX; j++) A[j-4]=A[j]+1;// case 3 雖然是先讀後寫,但假如4組資料組成一個向量,那麼同一組資料內無依賴的,因而可以向量化 // case 4 先寫後寫,無法向量化(此處無案例)for (j=1; j<MAX; j++) B[j]=A[j]+A[j-1]+1;//case 5 先讀後讀,因為沒有寫操作,不影響向量化for (j=1; j<MAX; j++) sum = sum + A[j]*B[j] //case 6 這種可以向量化,雖然每次都會讀同一個變數,再寫一個變數,因為可以先用一個寬暫存器表示sum,分別累加每一路資料,迴圈結束後再累加寬暫存器中的值。 for (i = 0; i < size; i++) { c[i] = a[i] * b[i]; }// case 7這種要確認c的記憶體空間和a/b的記憶體空間是否有交集。如果c是a或者b的別名,比如c=a+1,那麼c[i] = a[i+1],那a和c就有記憶體交集了。
for(int i = 0;i < 10;i++) a[i] = b[i] //這種較好for(int i =0,index=0;i < 10;i++) a[index++]=b[index] //這種無法向量化
五、手寫SIMD程式碼
5.1 SIMD程式碼例子和不同編譯器效能對比
const static char not_case_lower_bound = 'A';
const static char not_case_upper_bound= 'Z';
static void lowerStrWithSIMD(const char * src, const char * src_end, char * dst)
{
const auto flip_case_mask = 'A' ^ 'a';
const auto bytes_sse = sizeof(__m128i);
const auto * src_end_sse = src_end - (src_end - src) % bytes_sse;
const auto v_not_case_lower_bound = _mm_set1_epi8(not_case_lower_bound - 1);
const auto v_not_case_upper_bound = _mm_set1_epi8(not_case_upper_bound + 1);
const auto v_flip_case_mask = _mm_set1_epi8(flip_case_mask);
for (; src < src_end_sse; src += bytes_sse, dst += bytes_sse)
{
/// load 16 sequential 8-bit characters
const auto chars = _mm_loadu_si128(reinterpret_cast<const __m128i *>(src));
/// find which 8-bit sequences belong to range [case_lower_bound, case_upper_bound]
const auto is_not_case
= _mm_and_si128(_mm_cmpgt_epi8(chars, v_not_case_lower_bound), _mm_cmplt_epi8(chars, v_not_case_upper_bound));
/// keep lip_case_mask _mm_and_si128(v_flip_case_mask, is_not_case);
/// flip case by applying calculated mask
const auto xor_mask = _mm_and_si128(v_flip_case_mask, is_not_case);
const auto cased_chars = _mm_xor_si128(chars, xor_mask);
/// store result back to destination
_mm_storeu_si128(reinterpret_cast<__m128i *>(dst), cased_chars);
}
for (; src < src_end; ++src, ++dst)
if (*src >= not_case_lower_bound && *src <= not_case_upper_bound)
*dst = *src ^ flip_case_mask;
else
*dst = *src;
}
static void lowerStr(const char * src, const char * src_end, char * dst)
{
const auto flip_case_mask = 'A' ^ 'a';
for (; src < src_end; ++src, ++dst)
if (*src >= not_case_lower_bound && *src <= not_case_upper_bound)
*dst = *src ^ flip_case_mask;
else
*dst = *src;
}
上述兩個函式用於把字串中的大寫字母轉換成小寫字母,第一個函式採用了SIMD實現(採用128位指令),第二個函式採用了普通的做法。第一個是128位指令(16位元組),理論上相比非向量化指令,加速比為16倍。但是由於第二個程式碼在結構上是很清晰的,也可以自動向量化,在這裡我們測試下不同編譯器的編譯效能,g版本9.3.0,clang12.0.0。
5.2 解讀SIMD指令
最簡單的SIMD指令,實現兩個數字的加法:
const __m128i dst = _mm_add_epi32(left,right);
這條指令把4組int型別數字相加,填寫到結果中。__m128i代表是128位寬暫存器,存放的是int型別(4位元組32位),可以存放4個int型別。_mm_and_epi32是一個SIMD指令,_mm開頭表示128暫存器,add表示相加,epi32表示32位整數。SIMD指令的命名規範:在SIMD指令中,需要表達三個含義,分別是暫存器寬度、操作型別和引數寬度。
__m128i bitwiseNot(__m128i x){ const __m128i zero = _mm_setzero_si128(); const __128i one = _mm_cmpeq_epi32(zero, zero); return _mm_xor_si128(x, one);}
5.3 手寫SIMD指令的缺點
六、結論
來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70027828/viewspace-3001751/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 量化合約原始碼開發技術/合約量化系統開發技術原始碼搭建程式原始碼
- 合約量化交易開發丨量化交易AI機器人系統開發與技術程式碼示例AI機器人
- 藉助ai來分析程式碼,理解程式碼AI
- SET智慧合約量化(策略)系統開發技術(成熟技術程式碼)
- 量化交易系統開發程式碼部署方案丨合約量化系統開發技術成熟原始碼流程原始碼
- 量化秒合約技術開發丨現貨量化跟單系統程式設計開發及程式碼示例程式設計
- 前端程式碼質量的思考與實踐前端
- 向量化引擎對HTAP的價值與技術思考
- “量化交易”系統開發技術詳細丨“量化交易”原始碼原始碼
- 量化合約開發系統程式碼流程(Python)*合約量化系統開發定製技術流程Python
- Yes量化合約交易系統技術開發/原始碼/量化合約開發技術原始碼
- 聊聊Go程式碼覆蓋率技術與最佳實踐Go
- 量化交易系統開發技術闡述丨量化交易原始碼開發原始碼
- 在 Mac 上如何向 GitHub 提交程式碼?MacGithub
- 量化合約開發丨量化合約原始碼丨JAVA系統開發程式設計程式碼示例原始碼Java程式設計
- Python量化合約系統開發技術,合約量化原始碼系統開發技術方案Python原始碼
- 【量化跟單】合約量化跟單機器人系統技術開發程式(技術詳情)機器人
- 量化交易系統開發技術實現方案及原始碼原始碼
- 向Github上提交程式碼Github
- 量化原始碼丨量化合約機器人開發功能丨量化機器人系統技術解析原始碼機器人
- 量化合約系統開發(原始碼)合約量化系統開發(技術)原始碼
- 乾貨:如何藉助小程式雲開發實現小程式支付功能(含原始碼)原始碼
- 低程式碼平臺如何藉助Nginx實現閘道器服務Nginx
- 如何將過程程式碼變成物件導向的程式碼? - WLODEK物件
- Android端程式碼染色原理及技術實踐Android
- 研發團隊如何藉助Gitlab來做程式碼reviewGitlabView
- 量化合約系統開發(原始碼)合約量化系統開發(技術方案)原始碼
- 量化合約原始碼搭建開發案例丨合約量化技術開發成熟Demo原始碼
- 現貨策略跟單量化交易系統程式設計開發及程式碼示例(量化跟單)程式設計
- 物件導向與程式導向物件
- 程式導向與物件導向物件
- 量化合約機器人開發丨量化系統開發丨合約量化交易策略程式碼示例機器人
- 如何寫工程程式碼——重新認識物件導向物件
- 藉助前端技術構建QR碼生成器API前端API
- 量化合約系統開發(正式版)丨量化合約機器人程式設計程式碼示例機器人程式設計
- 量化合約系統開發(專案方案),合約量化系統開發(原始碼技術)原始碼
- 現貨量化/原始碼/秒合約/量化跟單交易系統合約開發python技術原始碼Python
- 開源低程式碼平臺開發實踐一:低程式碼開發探討與技術選型