jtr*_*avs 12 parallel-processing multithreading gcc openmp avx
我之前的评论(特别是@Zboson)之后我编辑了我的问题,以提高可读性
我一直采取行动并观察传统观点,即openmp线程的数量应与机器上的超线程数大致匹配,以获得最佳性能.但是,我观察到我的新笔记本电脑采用Intel Core i7 4960HQ,4核 - 8线程的奇怪行为.(请参阅此处的英特尔文档)
这是我的测试代码:
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <omp.h>
int main() {
const int n = 256*8192*100;
double *A, *B;
posix_memalign((void**)&A, 64, n*sizeof(double));
posix_memalign((void**)&B, 64, n*sizeof(double));
for (int i = 0; i < n; ++i) {
A[i] = 0.1;
B[i] = 0.0;
}
double start = omp_get_wtime();
#pragma omp parallel for
for (int i = 0; i < n; ++i) {
B[i] = exp(A[i]) + sin(B[i]);
}
double end = omp_get_wtime();
double sum = 0.0;
for (int i = 0; i < n; ++i) {
sum += B[i];
}
printf("%g %g\n", end - start, sum);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
当我使用gcc 4.9-4.9-20140209命令编译它时:gcc -Ofast -march=native -std=c99 -fopenmp -Wa,-q我在更改时看到以下性能OMP_NUM_THREADS[点数是5次运行的平均值,误差条(几乎看不到)是标准偏差]:

当显示为相对于OMP_NUM_THREADS = 1的加速时,绘图更清晰:

即使当omp线程的数量非常大于核心以及超线程数时,性能或多或少单调地增加了线程数!通常,当使用太多线程时(至少在我以前的经验中),由于线程开销,性能应该下降.特别是因为计算应该是cpu(或至少是内存)绑定而不是等待I/O.
更奇怪的是,加速是35倍!
有谁能解释一下?
我还用更小的阵列8192*4测试了它,并看到类似的性能缩放.
如果它很重要,我在Mac OS 10.9和运行获得的性能数据(在bash下):
for i in {1..128}; do
for k in {1..5}; do
export OMP_NUM_THREADS=$i;
echo -ne $i $k "";
./a.out;
done;
done > out
Run Code Online (Sandbox Code Playgroud)
编辑:出于好奇,我决定尝试更多的线程.我的操作系统将此限制为2000.奇怪的结果(加速和低线程开销)都说明了一切!

编辑:我在答案中尝试了@Zboson最新建议,即在循环中的每个数学函数之前放置VZEROUPPER,它确实解决了缩放问题!(它还将单线程代码从22秒发送到2秒!):

问题可能是由于该clock()功能.它不会在Linux上返回挂起时间.你应该使用这个功能omp_get_wtime().它比时钟更精确,适用于GCC,ICC和MSVC.事实上,即使我没有使用OpenMP,我也会将它用于计时代码.
我在这里测试了你的代码 http://coliru.stacked-crooked.com/a/26f4e8c9fdae5cc2
编辑:要考虑其可能导致您的问题的另一件事是exp和sin功能,您使用的是编译时没有AVX的支持.您的代码是使用AVX支持编译的(实际上是AVX2).如果您使用代码进行编译,您可以使用您的代码从GCC资源管理器中看到这一点.-fopenmp -mavx2 -mfma每当您使用AVX调用没有AVX支持的函数时,您需要将YMM寄存器的上半部分归零或支付大额罚金.您可以使用内在_mm256_zeroupper(VZEROUPPER)执行此操作.Clang为你做了这个,但最后我检查了GCC没有,所以你必须自己做(请参阅这个问题的评论运行任何英特尔AVX功能后,数学函数需要更多周期,这里的答案使用AVX CPU指令:性能不佳"/ arch:AVX").因此,由于未调用VZEROUPPER,因此每次迭代都会有很大的延迟.我不确定为什么这对多线程来说很重要,但如果GCC每次启动一个新线程时都这样做,那么它可以帮助解释你所看到的内容.
#include <immintrin.h>
#pragma omp parallel for
for (int i = 0; i < n; ++i) {
_mm256_zeroupper();
B[i] = sin(B[i]);
_mm256_zeroupper();
B[i] += exp(A[i]);
}
Run Code Online (Sandbox Code Playgroud)
编辑 一种更简单的测试方法是这样做而不是编译,-march=native不要设置arch(gcc -Ofast -std=c99 -fopenmp -Wa)或只使用SSE2(gcc -Ofast -msse2 -std=c99 -fopenmp -Wa).
编辑 GCC 4.8有一个选项-mvzeroupper,可能是最方便的解决方案.
此选项指示GCC在控制流转出函数之前发出vzeroupper指令,以最小化AVX到SSE转换惩罚以及删除不必要的zeroupper内在函数.