【问题标题】:Multithreading is slower than no threading C++多线程比无线程 C++ 慢
【发布时间】:2020-05-16 06:03:01
【问题描述】:

我是多线程编程的新手,我知道之前有人在 SO 上问过几个类似的问题,但是我想得到一个特定于我的代码的答案。

我有两个要循环的对象向量(v1 和 v2),根据它们是否满足某些条件,将这些对象添加到单个向量中,如下所示:

非多线程案例

std::vector<hobj> validobjs;
int length = 70;

for(auto i = this->v1.begin(); i < this->v1.end() ;++i) {
    if( !(**i).get_IgnoreFlag() && !(**i).get_ErrorFlag() ) {
        hobj obj(*i, length);
        validobjs.push_back(hobj);
    }
}

for(auto j = this->v2.begin(); j < this->v2.end() ;++j) {
    if( !(**j).get_IgnoreFlag() && !(**j).get_ErrorFlag() ) {
        hobj obj(*j, length);
        validobjs.push_back(hobj);
    }
}

多线程案例

std::vector<hobj> validobjs;
int length = 70;

#pragma omp parallel
{
    std::vector<hobj> threaded1;   // Each thread has own local vector
    #pragma omp for nowait firstprivate(length)
    for(auto i = this->v1.begin(); i < this->v1.end() ;++i) {
        if( !(**i).get_IgnoreFlag() && !(**i).get_ErrorFlag() ) {
            hobj obj(*i, length);
            threaded1.push_back(obj);
        }
    }

    std::vector<hobj> threaded2;  // Each thread has own local vector
    #pragma omp for nowait firstprivate(length)
    for(auto j = this->v2.begin(); j < this->v2.end() ;++j) {
        if( !(**j).get_IgnoreFlag() && !(**j).get_ErrorFlag() ) {
            hobj obj(*j, length);
            threaded2.push_back(obj);
        }
    }

    #pragma omp critical  // Insert local vectors to main vector one thread at a time
    {
        validobjs.insert(validobjs.end(), threaded1.begin(), threaded1.end());
        validobjs.insert(validobjs.end(), threaded2.begin(), threaded2.end());
    }
}

在非多线程情况下,我执行操作所花费的总时间比多线程情况快 4 倍左右(~1.5s vs ~6s)。

我知道 #pragma omp critical 指令会影响性能,但由于我事先不知道 validobjs 向量的大小,我不能依赖按索引随机插入。

所以问题:

1) 这种操作适合多线程吗?

2) 如果 1) 是 - 多线程代码看起来合理吗?

3) 我可以做些什么来提高性能以使其比无线程情况更快?

附加信息:

  • 上述代码嵌套在一个更大的代码库中,该代码库执行 10,000 - 100,000 次迭代(此循环未使用多线程)。我知道生成线程也会产生性能开销,但据我所知,这些线程一直保持活动状态,直到每次迭代都再次执行上述代码
  • omp_set_num_threads 设置为 32(我在 32 核机器上)。
  • Ubuntu,gcc 7.4

干杯!

【问题讨论】:

  • 我们可能需要更多代码才能继续。 v1v2 是什么?您能否发布更多代码,以便您的多线程代码易于执行?
  • 你如何测量时间?
  • @MarkLoeser 确定 - Intel® Xeon(R) Gold 6130 CPU @ 2.10GHz × 32 w/64gb RAM
  • 为什么有两个变量 threaded1 和 2?您是否希望 thread1 只被一个线程触及?
  • @Gilles 我正在使用 std::chrono::system_clock::now();在代码块之前和之后并将差异累积到静态变量

标签: c++ multithreading optimization openmp


【解决方案1】:

我不是多线程方面的专家,但我会尝试一下:

这种操作适合多线程吗?

我会说是的。特别是如果您有大量数据集,您可以进一步拆分它们,并行运行任意数量的过滤操作。但这取决于你要处理的数据量,线程创建和同步不是免费的。

与线程版本末尾的合并一样。

多线程代码看起来合理吗?

我认为你在让每个线程处理独立数据的正确道路上。

我可以做些什么来提高性能以使其比无线程情况更快?

我发现有几点可能会提高性能:

  1. 向量需要经常调整大小,这很昂贵。您可以使用reserve() 来预先保留内存,从而减少重新分配的次数(在最佳情况下为 0)。

  2. 最后两个向量的合并也是如此,这是一个关键点,先保留:

    validobjs.reserve(v1.size() + v2.size());
    

    然后合并。

  3. 将对象从一个向量复制到另一个向量可能会很昂贵,具体取决于您复制的对象的大小以及是否有执行更多代码的自定义复制构造函数。考虑只存储有效元素的索引或指向有效元素的指针。

  4. 您也可以尝试在结果向量中并行替换元素。如果默认构造一个元素很便宜并且复制有点昂贵,这可能会很有用。

    1. 像现在这样在两个线程中过滤数据。
    2. 同步它们并分配一个包含多个元素的向量:

      validobjs.resize(v1.size() + v2.size());
      
    3. 让每个线程在向量的独立部分上插入元素。例如,线程一将写入索引1x,线程2 写入索引x + 1validobjs.size() - 1

      尽管我不确定这是否完全合法,或者是否是未定义的行为


您也可以考虑使用std::list(链表)。连接链表或删除元素会在恒定时间内发生,但添加元素比使用保留内存的 std::vector 慢一些。


以上是我的想法,希望对你有用。

【讨论】:

    【解决方案2】:

    恕我直言,

    您将每个元素复制两次:复制到 threaded1/2 中,然后复制到 validobjs 中。 它会使你的代码变慢。

    您可以使用同步将元素添加到单个向量中。

    【讨论】:

    • 您能否举例说明如何做到这一点?
    • 尝试 omp_set_lock(&writelock) omp_unset_lock(&writelock);)。而且,您可以通过使用 emplace_back 而不是 push_back 来提高将元素插入向量的性能
    • 如果您能够指定结果向量的大小(近似大小优于零;使用 std::vector::reserve()),则可以提高代码的性能。
    • 但是同步也会消耗很多性能,具体取决于插入的次数。
    • #pragma omp critical{} 与使用 set 和 unset lock 有效吗?
    猜你喜欢
    • 2020-08-15
    • 1970-01-01
    • 1970-01-01
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    • 2017-11-17
    • 1970-01-01
    相关资源
    最近更新 更多