从pthread调用的OpenMP代码的性能问题

时间:2015-06-11 17:52:05

标签: c++ openmp

我尝试从I / O绑定操作异步执行一些计算。为此,我使用了一个使用OpenMP并行化循环的pthread。但是,与我在pthread中执行I / O绑定操作或者根本不创建pthread的情况相比,这会导致性能下降。

演示行为的最小示例(见下文)使用usleep来模拟I / O绑定任务。该程序的输出是

g++ simple.cpp -O3 -fopenmp
export OMP_PROC_BIND=false
No pthread: 34.0884
Compute from pthread: 36.6323
Compute from main: 34.1188

我不明白为什么在创建的pthread中使用OpenMP会有性能损失。对我来说更令人惊讶的是以下行为。

export OMP_PROC_BIND=true
./a.out                  
No pthread: 34.0635
Compute from pthread: 60.6081
Compute from main: 34.0347

为什么在运行时间中有两个因子?

源代码如下

#include <iostream>
using namespace std;
#include <unistd.h>

#include <omp.h>
int n = 1000000000;
int rep = 100;
double elapsed;

void compute() {
    double t = omp_get_wtime();
    double s = 0.0;
    #pragma omp parallel for reduction(+:s)
    for(int i=0;i<n;i++)
        s += 1/double(i+1);
    elapsed += omp_get_wtime()-t;
}

void* run(void *threadid) {
    compute();
}

void* wait(void* threadid) {
    usleep(150e3);
}

int main() {
    elapsed = 0;
    for(int k=0;k<rep;k++)
        compute();
    cout << "No pthread: " << elapsed << endl;

    elapsed = 0;
    for(int k=0;k<rep;k++) {
        pthread_t t; void* status;
        pthread_create(&t, NULL, run, NULL);
        usleep(150e3);
        pthread_join(t, &status);
    }
    cout << "Compute from pthread: " << elapsed << endl;

    elapsed = 0;
    for(int k=0;k<rep;k++) {
        pthread_t t; void* status;
        pthread_create(&t, NULL, wait, NULL);
        compute();
        pthread_join(t, &status);
    }
    cout << "Compute from main: " << elapsed << endl;
}

1 个答案:

答案 0 :(得分:0)

首先,一个公平的警告 - 从不受OpenMP管理的线程创建并行区域是非标准行为,可能会导致相当不便携的应用程序。

GNU OpenMP运行时库(libgomp)使用一个线程池来加速线程团队的创建。通过在TLS(线程局部存储)中的特殊结构中存储对它的引用,池与并行区域的主线程相关联。创建并行区域时,libgomp会查询主线程的TLS以查找线程池。如果不存在线程池,即它是该线程第一次创建并行区域,则会创建池。

当您从主线程调用compute()时,每次创建新的并行区域并使用它时,libgomp都会在TLS中找到对线程池的引用,因此产生线程的价格仅支付给第一次创建并行区域。

当您从第二个线程调用compute()时,libgomp无法在TLS中找到特殊结构,并与新线程池一起创建一个新结构。一旦线程终止,一个特殊的析构函数负责终止线程池。因此,每次调用compute()时都会创建线程池,因为您在外部循环的每个iteraton中生成一个新线程。 20 ms的开销(100次迭代的2秒差异)是此类操作的典型值。

我无法用OMP_PROC_BIND=true重现案例 - 程序基本上与没有它的情况相同,甚至因为绑定而更好。它可能是您的GCC版本,操作系统和CPU组合的特定内容。请注意,parallel for构造扩展了在团队的所有线程之间均匀分布的工作量。如果其中一个延迟,例如通过不得不将CPU核心与其他进程分时,它会延迟整个并行区域的完成。使用OMP_PROC_BIND=true时,不允许OS调度程序移动线程,并且必须使用来自其他进程的线程对其CPU内核进行分时。如果一个这样的外部线程使用了大量的CPU,那么当绑定线程时它会比它们没有时更有害,因为在后一种情况下,调度程序可以移动受影响的线程。换句话说,在特定情况下,当所有OpenMP线程必须共享除了一个核心以外的所有内核而不是延迟100%时,最好使整个程序延迟#cores / (#cores - 1)次,因为一个绑定线程必须共享其具有该外部线程的CPU核心50/50。当然,在这种情况下,人们会为昂贵的跨核移动操作付出代价,但如果外部影响太具破坏性,它仍然可以带来更好的利用率。