使用#pragma simd(即使使用-Ofast)或依赖编译器自动矢量化更多地说明了为什么盲目地期望编译器有效地实现SIMD 是一个坏主意。为了有效地使用 SIMD,您需要使用数组结构数组。例如对于 SIMD 宽度为 4 的单浮点数,您可以使用
//struct of arrays of four complex numbers
struct c4 {
float x[4]; // real values of four complex numbers
float y[4]; // imaginary values of four complex numbers
};
这里的代码展示了如何使用 SSE 为 x86 指令集执行此操作。
#include <stdio.h>
#include <x86intrin.h>
#define N 10
struct c4{
float x[4];
float y[4];
};
static inline void cabs_soa4(struct c4 *a, float *b) {
__m128 x4 = _mm_loadu_ps(a->x);
__m128 y4 = _mm_loadu_ps(a->y);
__m128 b4 = _mm_sqrt_ps(_mm_add_ps(_mm_mul_ps(x4,x4), _mm_mul_ps(y4,y4)));
_mm_storeu_ps(b, b4);
}
int main(void)
{
int n4 = ((N+3)&-4)/4; //choose next multiple of 4 and divide by 4
printf("%d\n", n4);
struct c4 a[n4]; //array of struct of arrays
for(int i=0; i<n4; i++) {
for(int j=0; j<4; j++) { a[i].x[j] = 1, a[i].y[j] = -1;}
}
float b[4*n4];
for(int i=0; i<n4; i++) {
cabs_soa4(&a[i], &b[4*i]);
}
for(int i = 0; i<N; i++) printf("%.2f ", b[i]); puts("");
}
多次展开循环可能会有所帮助。在任何情况下,这对于大型N 来说都是没有意义的,因为该操作受内存带宽限制。对于大 N(意味着当内存使用量远大于最后一级缓存时),虽然#pragma omp parallel 可能会有所帮助,但最好的解决方案是不要对大 N 执行此操作。而是在适合最低级别的块中执行此操作缓存以及其他计算操作。我的意思是这样的
for(int i = 0; i < nchunks; i++) {
for(int j = 0; j < chunk_size; j++) {
b[i*chunk_size+j] = cabs(a[i*chunk_size+j]);
}
foo(&b[i*chunck_size]); // foo is computationally intensive.
}
我没有在这里实现数组结构的数组,但是调整代码应该很容易。