问题描述
我有一个for循环,看起来像这样:
for (int i = 0;i<N;i++) {
vector<double> vec;
//then do work on vec,such as resize or push_back
}
这是低效的,因为每次循环时都会调整vec的大小,这可能会强制每次通过for循环强制进行动态内存分配。因此,简单的优化方法是:
vector<double> vec;
for (int i = 0;i<N;i++) {
vec.clear();
//then do work on vec,such as resize or push_back
}
这更快,因为clear不会在vec中释放内存,因此我们不必每次都重新分配内存。
但是如果我想用openmp并行化for循环怎么办?我不能让所有线程都共享一个向量“ vec”。如此看来,我需要回到第一个选项,并在每次循环时都重新初始化向量,如下所示:
#pragma omp parallel for
for (int i = 0;i<N;i++) {
vector<double> vec;
//then do work on vec,such as resize or push_back
}
有没有一种方法可以避免这种低效率并避免每次重新分配向量?这样做会安全吗?
vector<vector<double>> outervec;
outervec.resize(omp_get_max_threads());
#pragma omp parallel for shared(outervec)
for (int i = 0;i<N;i++) {
int tid = omp_get_thread_num();
vector<double> &vec = outervec[tid];
vec.clear();
//then do work on vec,such as resize or push_back
}
调整vec的大小时,它可能会变得很大,并且通过for循环N的次数也可能会很大。当对大量内存进行多次处理时,在向量中分配内存的速度很慢。这样做的目的是避免每次都要重新分配存储在vec中的动态分配内存,而不必进行重新分配。关心的不是矢量对象的堆栈分配的内存占用空间(它很小且分配迅速),而是属于矢量对象的堆分配的内存。
解决方法
实际上非常简单。 #pragma omp parallel for
是复合语句-您可以将其拆分:
#pragma omp parallel
{
std::vector<double> vec;
#pragma omp for
for (int i = 0;i<N;i++) {
vec.clear();
//then do work on vec,such as resize or push_back
}
}
这将按您的预期工作。
这是一个明显的例子,其中为每个循环初始化向量的“干净”解决方案通常会引起性能损失。
有时,如果此“缓存”向量是全局/静态变量,则可能要使用#pragma omp threadprivate
指令。
您建议的解决方案:
std::vector<std::vector<double>> outervec;
outervec.resize(omp_get_max_threads());
#pragma omp parallel for shared(outervec)
for (int i = 0;i<N;i++) {
int tid = omp_get_thread_num();
auto& vec = outervec[tid];
vec.clear();
//then do work on vec,such as resize or push_back
}
可以工作,但是还有另一个巨大的性能问题。这很可能会引入错误共享-多个std::vector
实例使用的指针存储在同一缓存行中。如果经常修改这些内容,即使用push_back
,则会影响性能。这可能比“干净”的解决方案更糟糕。
如果由于某些原因必须从外部引入矢量。使用firstprivate
创建私人副本,即:
std::vector<double> vec;
#pragma omp parallel for firstprivate(vec)
for (int i = 0;i<N;i++) {
vec.clear();
//then do work on vec,such as resize or push_back
}
不要使用private(vec)
,因为它会使变量未初始化,并且vec.clear()
会爆炸。