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

时间:2014-06-26 20:58:12

标签: 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 );
}

示例输出(在我们中):

1034.069001
1058.620000
1034.572000
2210.681000
18234.355000

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

1 个答案:

答案 0 :(得分: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

这是循环的汇编代码

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

这几乎与循环的程序集

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

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

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

编辑: OpenMP 3.1规范的第2.41节标题为“确定并行区域的线程数”,给出了确定线程数的算法。 The source code for GOMP_parallel from GCC-4.8表示它调用的第一个函数是gomp_resolve_num_threads