【发布时间】:2015-08-01 04:58:12
【问题描述】:
我需要计算对象向量中所有元素i,j 之间的交互。在大小为N 的向量中,这相当于(N*(N-1))/2 的计算,并且会天真地在嵌套的 for 循环中解决,如下所示:
for( unsigned int i = 0; i < vector.size()-1; i++ ) {
for ( unsigned int j = i+1; j < vector.size(); j++ ) {
//compute interaction between vector[i] and vector[j]
}
}
困难在于尝试使用 OpenMP 并行化加速该过程。随着i 的增加,内部循环中的计算次数线性减少。据我了解,#pragma omp parallel for 将循环除以使用的线程数。尽管外部循环会被平均划分,但实际计算不会。例如,长度为 257 的向量将进行 (257*256)/2=32896 次计算。如果 OpenMP 平均分割外循环(线程 1 的 i=0...127,线程 2 的 i=128...255),线程 1 必须计算 24640 次交互,而线程 2 必须计算 8256 次交互,取大约 75% 的时间,总效率为 62%。在 4 个线程之间拆分外循环将花费约 44% 的时间,效率约为 57%。我可以验证这是 MCVE 的问题
#include <iostream>
#include <unistd.h>
#include <omp.h>
#include <vector>
#include <ctime>
int main()
{
timespec sleepTime;
sleepTime.tv_sec = 0;
sleepTime.tv_nsec = 1e6; // 1 ms
std::vector< int > dummyVector(257,0);
#pragma omp parallel for
for(unsigned int i = 0; i < dummyVector.size()-1; i++ ) {
for(unsigned int j = i+1; j < dummyVector.size(); j++ ) {
// calculate( dummyVector[i], dummyVector[j] );
nanosleep(&sleepTime,NULL);
}
}
return 0;
}
使用 nanosleep 模拟我的交互,2 线程和 4 线程版本分别耗时 75% 和 44%
[me@localhost build]$ export OMP_NUM_THREADS=1
[me@localhost build]$ time ./Temp
real 0m38.242s ...
[me@localhost build]$ export OMP_NUM_THREADS=2
[me@localhost build]$ time ./Temp
real 0m28.576s ...
[me@localhost build]$ export OMP_NUM_THREADS=4
[me@localhost build]$ time ./Temp
real 0m16.715s ...
如何更好地平衡线程间的计算?有没有办法告诉 OpenMP 不连续地拆分外循环?
为了将嵌套的 for 循环移出 omp 并行块,我尝试预先计算所有可能的索引对,然后遍历这些对
std::vector< std::pair < int, int > > allPairs;
allPairs.reserve((dummyVector.size()*(dummyVector.size()-1))/2);
for(unsigned int i = 0; i < dummyVector.size()-1; i++ ) {
for(unsigned int j = i+1; j < dummyVector.size(); j++ ) {
allPairs.push_back(std::make_pair<int,int>(i,j));
}
}
#pragma omp parallel for
for( unsigned int i = 0; i < allPairs.size(); i++ ) {
// calculate( dummyVector[allPairs[i].first],
// dummyVector[allPairs[i].second] );
nanosleep(&sleepTime,NULL);
}
这确实有效地平衡了跨线程的计算,但它引入了索引对的不可避免的串行构造,随着N 的增长,这将损害我的运行时间。我还能做得更好吗?
【问题讨论】:
-
如果你能想出一个方程来确定给定 N 的对,你就不必预先计算所有的对。
-
是的,有一种方法可以告诉 OpenMP 将外循环分成不同大小的块。调查
schedule子句的使用情况,该子句正好针对您遇到的问题类型,使用(通常)默认的static计划时的负载不平衡。 -
@HighPerformanceMark 我知道我不可能是第一个遇到这个问题的人。
schedule (dynamic)和schedule(guided)(使用重新排序的外部循环首先进行较短的计算)都显着改善了负载平衡。如果您想在答案中阐述您的评论,我可以接受。否则我稍后会写一个答案来解释细节。
标签: c++ multithreading openmp