我正在寻找一些关于如何与SSE做一个并行前缀总和的建议。我有兴趣通过一系列整数,浮点数或双精度来做这件事。与SSE并行前缀(累计)总和
我已经想出了两个解决方案。特例和一般情况。在这两种情况下,解决方案都以两次与OpenMP并行的方式在阵列上运行。对于特殊情况,我在两次通行证上都使用SSE。对于一般情况,我只在第二阶段使用它。
我的主要问题是我如何在一般情况下的第一次使用SSE?以下链接simd-prefix-sum-on-intel-cpu显示字节的改进,但不显示32位数据类型。
特殊情况被称为特殊的原因是它需要数组是特殊的格式。例如,让我们假设浮点数只有16个元素的数组a
。然后,如果阵列重新排列是这样的(结构的数组为结构阵列):
a[0] a[1] ...a[15] -> a[0] a[4] a[8] a[12] a[1] a[5] a[9] a[13]...a[3] a[7] a[11] a[15]
SSE垂直求和可以在两个道次中使用。但是,如果数组已经处于特殊格式并且输出可以以特殊格式使用,则这将仅有效。否则,必须在输入和输出上进行昂贵的重新排列,这会使其比一般情况慢得多。
也许我应该考虑前缀和的不同算法(例如二叉树)?
代码一般情况下:
void prefix_sum_omp_sse(double a[], double s[], int n) {
double *suma;
#pragma omp parallel
{
const int ithread = omp_get_thread_num();
const int nthreads = omp_get_num_threads();
#pragma omp single
{
suma = new double[nthreads + 1];
suma[0] = 0;
}
double sum = 0;
#pragma omp for schedule(static) nowait //first parallel pass
for (int i = 0; i<n; i++) {
sum += a[i];
s[i] = sum;
}
suma[ithread + 1] = sum;
#pragma omp barrier
#pragma omp single
{
double tmp = 0;
for (int i = 0; i<(nthreads + 1); i++) {
tmp += suma[i];
suma[i] = tmp;
}
}
__m128d offset = _mm_set1_pd(suma[ithread]);
#pragma omp for schedule(static) //second parallel pass with SSE as well
for (int i = 0; i<n/4; i++) {
__m128d tmp1 = _mm_load_pd(&s[4*i]);
tmp1 = _mm_add_pd(tmp1, offset);
__m128d tmp2 = _mm_load_pd(&s[4*i+2]);
tmp2 = _mm_add_pd(tmp2, offset);
_mm_store_pd(&s[4*i], tmp1);
_mm_store_pd(&s[4*i+2], tmp2);
}
}
delete[] suma;
}
虽然像gcc/icc这样的编译器可以为第二部分做自动矢量化,所以你不需要使用SIMD内在函数。你有没有得到性能改进,比较普通的c代码和一些编译器选项,比如'-msse2' – kangshiyin
他们可能。我把它在MSVC2013上。它不会自动矢量化第二遍。我对MSVC的经验是,当你使用OpenMP时,你必须自己做矢量化。我不认为他们中的任何一个都会用SSE代码展开循环,但在这种情况下无论如何都无济于事。 –
为了回应性能问题,我发布的通用代码比在发布模式下的顺序代码快3倍,AVX在我的4核心ivy桥系统上启用。时间成本应该是'n/ncores *(1 + 1/SIMD_width)'。所以对于4核和SIMD_width = 2(双精度)应该是3n/8。这大约增加2.7倍。超线程有一点帮助,所以我推测它超过了3(我使用8个线程,当我尝试4个线程时,性能下降了一点)。 –