如何避免每次通过 openmp for 循环重新初始化向量?
How to avoid reinitializing a vector every time though an openmp for loop?
我有一个 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 中的动态分配内存。关注的不是 vector 对象的堆栈分配内存占用(小且分配快),而是属于 vector 对象的堆分配内存。
其实很简单。 #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
指令,如果这个“缓存”-vector 是全局/静态变量。
您建议的解决方案:
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
}
会工作,但还有另一个巨大的性能问题。这很可能会引入 false-sharing - 多个 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()
会爆炸。
我有一个 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 中的动态分配内存。关注的不是 vector 对象的堆栈分配内存占用(小且分配快),而是属于 vector 对象的堆分配内存。
其实很简单。 #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
指令,如果这个“缓存”-vector 是全局/静态变量。
您建议的解决方案:
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
}
会工作,但还有另一个巨大的性能问题。这很可能会引入 false-sharing - 多个 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()
会爆炸。