parallel_for中的多线程替代互斥锁

时间:2017-07-18 07:39:40

标签: c++ multithreading performance mutex ppl

我对C ++很新,所以请原谅这是一个愚蠢的问题,但我没有找到我在互联网上寻找的好例子。

基本上我正在使用parallel_for循环来查找2D数组中的最大值(以及其间的一堆其他操作)。首先,我甚至不知道这是否是最好的方法,但考虑到这个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];
});

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

有没有关于如何修改类似代码以使其更快的好例子?

1 个答案:

答案 0 :(得分:4)

正如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 。通常,除非两个端受原子语义或锁和内存栅栏保护,否则不得读取同时写入的值。原因是变量访问很可能包含多个内存访问,具体取决于硬件支持的类型。

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

   a1  a2  a3  a4  b1  b2  b3  b4
1 480 770 601 953 469 750 588 944
2   0   0   0   0   0   0   0   0
3   3  13   9  12   3  12   9  12
4   0   2   4   3   0  14   3   2
5   0   0  11   0   0   0  11   0
6 165 292 162 313 180 368 116 368

Locks are hard.

您还可以使用原子和compute the maximum on that。但是,我猜测 1 它在循环 2 中仍然表现不佳,而在循环之外,无论你是使用原子还是锁定都无关紧要。< / p>

1:如果有疑问,请不要猜测 - 测量!

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