我尝试从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;
}
答案 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。当然,在这种情况下,人们会为昂贵的跨核移动操作付出代价,但如果外部影响太具破坏性,它仍然可以带来更好的利用率。