当num_threads变化时,OpenMP并行区域开销增加

Rai*_*inn 5 c++ multithreading openmp

我试图在程序的不同部分使用不同数量的线程,以实现最大的加速。但是,发现使用num_threads子句切换线程号会产生大量开销。我正在寻找对此的解释,因为根据我的理解,线程池应始终包含给定数量的线程,而不管调用的实际数量是多少。我也在寻找与此相对的可能解决方法。谢谢。

样例代码:

#include<cstdio>
#include<omp.h>

void omp_sum(int ntd) {
    int s = 0;
    #pragma omp parallel num_threads(ntd)
    {
        int i = omp_get_thread_num();
        #pragma omp atomic
        s += i;
    }
}   

int main()
{
    int N = 100;
    int NT1 = 6, NT2 = 12;
    double t;

    t = omp_get_wtime();
    for(int n=0;n<N;n++) {
        omp_sum(NT1);
    }
    printf("%lf\n", (omp_get_wtime() - t) * 1e6 );

    t = omp_get_wtime();
    for(int n=0;n<N;n++) {
        omp_sum(NT2);
    }
    printf("%lf\n", (omp_get_wtime() - t) * 1e6 );

    t = omp_get_wtime();
    for(int n=0;n<N;n++) {
        omp_sum(NT1);
        omp_sum(NT1);
    }
    printf("%lf\n", (omp_get_wtime() - t) * 1e6 );

    t = omp_get_wtime();
    for(int n=0;n<N;n++) {
        omp_sum(NT2);
        omp_sum(NT2);
    }
    printf("%lf\n", (omp_get_wtime() - t) * 1e6 );

    t = omp_get_wtime();
    for(int n=0;n<N;n++) {
        omp_sum(NT1);
        omp_sum(NT2);
    }
    printf("%lf\n", (omp_get_wtime() - t) * 1e6 );
}
Run Code Online (Sandbox Code Playgroud)

样本输出(以美国为单位):

1034.069001
1058.620000
1034.572000
2210.681000
18234.355000
Run Code Online (Sandbox Code Playgroud)

编辑:运行代码的工作站具有2个六核Intel E5-2630L CPU,因此应该总共有12个硬件核心和24个超线程。我在GCC 4.8.2中使用Fedora 19。

Z b*_*son 2

我可以在我的四核系统/八超线程系统上使用 GCC 4.8 (g++ -O3 -fopenmp foo.cpp) 重现您的结果。我把N1改为4,N2改为8。

你的功能omp_sum很简单

pushq   %rbx    
movq    %rdi, %rbx
call    omp_get_thread_num
movq    (%rbx), %rdx
lock addl   %eax, (%rdx)
popq    %rbx
ret
Run Code Online (Sandbox Code Playgroud)

这是循环的汇编代码

for(int n=0;n<N;n++) {
    omp_sum(NT1);
    omp_sum(NT2);
}

.L10
leaq    32(%rsp), %rsi
xorl    %ecx, %ecx
movl    $4, %edx
movl    $_Z7omp_sumi._omp_fn.0, %edi
movl    $0, 28(%rsp)
movq    %rbx, 32(%rsp)
call    GOMP_parallel
leaq    32(%rsp), %rsi
xorl    %ecx, %ecx
movl    $8, %edx
movl    $_Z7omp_sumi._omp_fn.0, %edi
movl    $0, 28(%rsp)
movq    %rbx, 32(%rsp)
call    GOMP_parallel
subl    $1, %ebp
jne .L10
Run Code Online (Sandbox Code Playgroud)

这与循环的组装几乎相同

for(int n=0;n<N;n++) {
    omp_sum(NT2);
    omp_sum(NT2);
}
Run Code Online (Sandbox Code Playgroud)

唯一的变化是movl $4, %edx代替movl $8, %edx. 因此很难看出是什么原因导致了问题。所有的魔法都发生在 GOMP_parallel 中。人们必须查看 GOMP_parallel 的源代码,但我的猜测是 GOMP_parallel 检查并行调用中最后使用的线程数,如果新的并行调用使用不同数量的线程,则需要切换一些开销。这个开销比你的简单函数大得多。

但我不确定为什么这会成为一个问题。在实践中,使用如此短的并行部分是没有意义的(一个可以并行化一个循环,N 会更大),所以开销应该不成问题。

编辑: OpenMP 3.1 规范的第 2.41 节标题为“确定并行区域的线程数”给出了确定线程数的算法。GCC-4.8 中 GOMP_parallel 的源代码显示它调用的第一个函数是gomp_resolve_num_threads.