【问题标题】:Multithreading alternative to mutex in parallel_for多线程替代parallel_for中的互斥锁
【发布时间】:2017-12-22 22:44:02
【问题描述】:

我对 C++ 还很陌生,因此如果这是一个愚蠢的问题,请原谅,但我没有在互联网上找到我正在寻找的好例子。

基本上,我使用 parallel_for 循环来查找 2D 数组中的最大值(以及介于两者之间的一堆其他操作)。首先我什至不知道这是否是最好的方法,但考虑到这个二维数组的长度,我认为拆分计算会更快。

我的代码:

vector<vector<double>> InterpU(1801, vector<double>(3601, 0));
Concurrency::parallel_for(0, 1801, [&](int i) {

    long k = 0; long l = 0;
    pair<long, long> Normalized;
    double InterpPointsU[4][4];
    double jRes;
    double iRes = i * 0.1;
    double RelativeY, RelativeX;
    int p, q;

    while (iRes >= (k + 1) * DeltaTheta) k++;
    RelativeX = iRes / DeltaTheta - k;
    for (long j = 0; j < 3600; j++)
    {
        jRes = j * 0.1;
        while (jRes >= (l + 1) * DeltaPhi) l++;
        RelativeY = jRes / DeltaPhi - l;
        p = 0;
        for (long m = k - 1; m < k + 3; m++)
        {
            q = 0;
            for (long n = l - 1; n < l + 3; n++)
            {
                Normalized = Normalize(m, n, PointsTheta, PointsPhi);
                InterpPointsU[p][q] = U[Normalized.first][Normalized.second];
                q++;
            }
            p++;
        }
        InterpU[i][j] = bicubicInterpolate(InterpPointsU, RelativeX, RelativeY);
        if (InterpU[i][j] > MaxU)
        {
            SharedDataLock.lock();
            MaxU = InterpU[i][j];
            SharedDataLock.unlock();
        }
    }
    InterpU[i][3600] = InterpU[i][0];
});

您可以在这里看到我使用了一个名为SharedDataLockmutex 来保护访问同一资源的多个线程。 MaxU 是一个变量,应该只包含 InterpU 向量的最大值。 该代码运行良好,但由于我遇到速度性能问题,我开始研究atomic 和其他一些东西。

有没有什么好的例子可以说明如何修改类似的代码以使其更快?

【问题讨论】:

  • 没有必要共享MaxU 变量。每个线程应该在它的向量范围中找到最大值,然后你需要在线程结果中找到最大值。
  • 看看std::atomic。它是另一种同步工具。
  • 您的代码不完整,请发布一个完整的示例来描述您的问题...无论如何,您可以通过使用原子操作来避免锁定。
  • 您是否考虑过将combinable 用于MaxU?即每个线程都有自己的最大值,稍后使用max 组合。
  • @VTT 最后我需要一个包含所有数据的向量和一个最大值来规范化它。

标签: c++ multithreading performance mutex ppl


【解决方案1】:

正如VTT所说,你可以简单地找到每个线程的局部最大值,然后将它们合并使用combinable

Concurrency::combinable<double> CombinableMaxU;
Concurrency::parallel_for(0, 1801, [&](int i) {
    ...
        CombinableMaxU.local() = std::max(CombinableMaxU.local(), InterpU[i][j]);
}
MaxU = std::max(MaxU, CombinableMaxU.combine(std::max<double>));

请注意,您当前的代码实际上是错误(除非MaxU 是原子的),您在锁之外读取MaxU,而它可以被其他线程同时写入。通常,您不能读取同时写入的值,除非双方都受到原子语义或锁和内存栅栏的保护。一个原因是变量访问很可能由多个内存访问组成,具体取决于硬件如何支持该类型。

但在你的情况下,你甚至有一个经典的竞争条件:

MaxU == 1
  Thread a                 |   Thread b
InterpU[i][j] = 3          | InterpU[i][j] = 2
if (3 > MaxU)              |  if (2 > MaxU)
SharedDataLock.lock();     | SharedDataLock.lock();
(gets the lock)            | (waiting for lock)
MaxU = 3                   | ...
SharedDataLock.unlock();   | ...
...                        | (gets the lock)
                           | MaxU = 2
                           | SharedDataLock.unlock();
MaxU == 2

Locks are hard.

您也可以使用原子和compute the maximum on that。但是,我猜1它在循环内仍然不能很好地执行2,而在循环外,你使用原子还是锁都没有关系。

1:有疑问时,不要猜测 - 测量!

2:仅仅因为某些东西是原子的并且由硬件支持,并不意味着它与访问本地数据一样高效。首先,原子指令通常比它们的非原子指令成本更高,其次你必须处理非常糟糕的缓存效果,因为核心/缓存会争夺数据的所有权。虽然在许多情况下原子可能更优雅(恕我直言,不是这一次),但大多数情况下减少速度更快。

【讨论】:

  • 如果我没记错的话,你应该在循环中访问和分配CombinableMaxU.local()。此外,假设它的先前值无关紧要,您可能只用MaxU = CombinableMaxU.combine(std::max&lt;double&gt;); 完成。
  • @Hasturkun 忘记了.local(),谢谢。 TBH 我对 PPL 不是很熟悉,虽然它看起来很直观。我希望我的答案是惯用的,而不是我在编辑之前使用的通用手动解决方案。从问题中我不知道MaxU 的先前值是否重要,所以我只使用语义等效的代码。
  • @Zulan,我不明白当前代码是怎么错的,因为无论 MaxU 包含什么绝对最大值......我实际上并不关心最大值与的顺序它以前的价值......无论如何,非常感谢您的回答,并且看起来正是我正在寻找的。谢谢你教会了我一些新东西!
  • @Noldor130884 我添加了更详细的解释,说明您当前的代码在我的答案中出错的各种原因。
  • @Zulan 很抱歉再次打扰,但 MaxU = CombinableMaxU.combine(std::max&lt;double&gt;);MaxU = std::max(MaxU, CombinableMaxU.combine(std::max&lt;double&gt;)); 都不起作用。我错过了什么吗?我如何申报 MaxU?
猜你喜欢
  • 2013-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-19
  • 2012-12-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多