【问题标题】:How to avoid reinitializing a vector every time though an openmp for loop?如何避免每次通过 openmp for 循环重新初始化向量?
【发布时间】:2020-10-23 06:06:28
【问题描述】:

我有一个看起来像这样的 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 中的动态分配内存。关心的不是向量对象的堆栈分配内存占用(很小且分配速度快),而是属于向量对象的堆分配内存。

【问题讨论】:

  • 你希望每个线程使用自己的向量,或者不。如果是,则需要为每个线程创建向量,并且似乎是在创建线程之前创建所有向量,还是每个线程创建自己的向量,工作量在逻辑上是相同的,并且会实际上受益于并行化。
  • 每个线程都有自己的堆栈,所以无论你尝试它(使用例如 thread_local)你都会有一个新的向量。
  • @MichaelChourdakis - 我编辑了问题以添加共享(outervec),所以现在向量的向量在线程之间显式共享。无论如何,我不确定这是否是正确的解决方案 - 我正在寻找建议。
  • 我的意思是听:看看这个:godbolt.org/z/Whv63o 现在将代码更改为局部变量。组装更简单,可能更快。您还假设 clear 不会解除分配和重新分配,不知道为什么。除非分析显示存在瓶颈,否则始终使用更简单的代码,然后可以进行技巧和黑客攻击。编译器相对聪明。要回答您的问题,outervec 技巧应该可以正常工作。不过不推荐。
  • @Adam 英特尔 TBB 向量可能是矫枉过正。这是一个更复杂的数据结构,需要更改编译器等。

标签: c++ openmp hpc


【解决方案1】:

其实很简单。 #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
    }
}

这会像您期望的那样正常工作。

这是一个明显的案例,其中为每个循环初始化向量的“干净”解决方案具有通常足够相关的性能损失。..

如果此“缓存”-vector 是全局/静态变量,有时您可能想要使用 #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() 会爆炸。

【讨论】:

  • 谢谢!这完全有道理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-08
相关资源
最近更新 更多