SSE指令集實現reduce歸約操作
阿新 • • 發佈:2019-01-12
通俗的說這裡的歸約就是求和,比如:
for(i = 0; i < n; i++){
sum += a[i];
}
這裡主要對比較正常寫程式碼的歸約和迴圈展開以及SSE指令向量歸約操作的比較。
話不多說,上程式碼:
#include <stdio.h>
#include <x86intrin.h>
#include <time.h>
#include <stdlib.h>
//正常處理的陣列歸約
int normal_add(int *a, int n){
int sum = 0;
int i;
for(i = 0; i < n; i++){
sum += a[i];
}
return sum;
}
//正常陣列歸約的四層迴圈展開
int normal_add_loop(int *a, int n){
int sum = 0;
int i;
int block = n / 4;
int reserve = n % 4;
for(i = 0; i < block; i++){
sum += *a;
sum += *(a+1);
sum += *(a+2);
sum += *(a+3);
a += 4;
}
for(i = 0; i < reserve; i++){
sum + = a[i];
}
return sum;
}
//sse指令向量歸約(一個暫存器,一次取4個數)
int sse_add(int *a, int n){
int sum = 0;
__m128i sse_sum = _mm_setzero_si128();
__m128i sse_load;
int i;
int block = n / 4; // SSE暫存器能一次處理4個32位的整數
int reserve = n % 4; // 剩餘的不足16位元組
__m128i *p = (__m128i *)a;
for(i = 0; i < block; i++) {
sse_load = _mm_load_si128(p + i);
sse_sum = _mm_add_epi32(sse_sum, sse_load); // 帶符號32位緊縮加法
}
// 剩餘的不足16位元組
for(i = 0; i < reserve; i++) {
sum += a[i + block * 4];
}
// 將累加值合併
sse_sum = _mm_hadd_epi32(sse_sum, sse_sum); // 帶符號32位水平加法
sse_sum = _mm_hadd_epi32(sse_sum, sse_sum);
sum += _mm_cvtsi128_si32(sse_sum); // 返回低32位
// 或者 sum += sse_sum[0];
return sum;
}
//sse指令向量歸約(四個暫存器,一次迴圈去16個值)
int sse_add_loop4(int *a, int n){
int sum = 0;
int i;
//四個sse暫存器一次處理16 個數
int block = n / 16;
//不足16個數的部分
int reserve = n % 16;
__m128i sse_sum0 = _mm_setzero_si128();
__m128i sse_sum1 = _mm_setzero_si128();
__m128i sse_sum2 = _mm_setzero_si128();
__m128i sse_sum3 = _mm_setzero_si128();
__m128i sse_load0;
__m128i sse_load1;
__m128i sse_load2;
__m128i sse_load3;
__m128i *p = (__m128i *)a;
for(i = 0; i < block; i++){
sse_load0 = _mm_load_si128(p);
sse_load1 = _mm_load_si128(p + 1);
sse_load2 = _mm_load_si128(p + 2);
sse_load3 = _mm_load_si128(p + 3);
sse_sum0 = _mm_add_epi32(sse_sum0, sse_load0);
sse_sum1 = _mm_add_epi32(sse_sum1, sse_load1);
sse_sum2 = _mm_add_epi32(sse_sum2, sse_load2);
sse_sum3 = _mm_add_epi32(sse_sum3, sse_load3);
p = p+4;
}
//不足16個數的部分
int dev = block * 16;
for(i = 0; i < reserve; i++){
sum += a[i + dev];
}
sse_sum0 = _mm_add_epi32(sse_sum0, sse_sum1);
sse_sum2 = _mm_add_epi32(sse_sum2, sse_sum3);
sse_sum0 = _mm_add_epi32(sse_sum0, sse_sum2);
sse_sum0 = _mm_hadd_epi32(sse_sum0, sse_sum0);
sse_sum0 = _mm_hadd_epi32(sse_sum0, sse_sum0);
sum += _mm_cvtsi128_si32(sse_sum0); // 取低32位
return sum;
}
//陣列賦值
void value (int *a, int n){
int i, t = 1;
for(i = 0; i < n; i++){
a[i] = t;
if(i > 0 && i % 10000 == 0){
t++;
}
}
}
//列印陣列
void print(int *a, int n){
int i;
for(i = 0; i < n; i++){
printf("%d ", a[i]);
}
}
int main(){
clock_t s, e;
int n = 100000000;
int *a = malloc(sizeof(int) * n);
value(a, n);
//normal_add_sum
s = clock();
int normal_add_sum = normal_add(a , n);
e = clock();
float time = (float)(e - s) / CLOCKS_PER_SEC;
printf("normal_add_sum = %d, time = %f \n",normal_add_sum, time);
//normal_add_loop_sum
s = clock();
int normal_add_loop_sum = normal_add_loop(a, n);
e = clock();
time = (float)(e - s) / CLOCKS_PER_SEC;
printf("normal_add-loop_sum = %d, time = %f \n", normal_add_loop_sum, time);
//sse_add_sum
s = clock();
int sse_add_sum = sse_add(a, n);
e = clock();
time = (float)(e - s) / CLOCKS_PER_SEC;
printf("sse_add_sum = %d, time = %f \n", sse_add_sum, time);
//sse_add_loop_sum
s = clock();
int sse_add_loop4_sum = sse_add_loop4(a, n);
e = clock();
time = (float)(e - s) / CLOCKS_PER_SEC;
printf("sse_add_loop4_sum = %d, time = %f \n", sse_add_loop4_sum, time);
free(a);
}
編譯:
clang -mssse3 reduce.c -o reduce
執行:
./reduce
結果: