【问题标题】:Testing 2 arrays for equality in a parallel for loop在并行 for 循环中测试 2 个数组是否相等
【发布时间】:2013-11-09 17:43:17
【问题描述】:

我目前有一个 for 循环来比较 2 个数组并确定它们是否相等。

public override bool Equals(object obj)
{
    RushHourPathLengthNode otherNode = (RushHourPathLengthNode)obj;

    // Compare their carCoords and return false as soon as we find a difference
    for (int i = 0, l = carCoords.Length; i < l; ++i)
        if (carCoords[i].x != otherNode.carCoords[i].x || carCoords[i].y != otherNode.carCoords[i].y)
            return false;
    return true;
}

这很好用,但它是我的程序中最慢的部分之一。这样我的测试用例大约需要 7 秒来计算。

虽然我可能有 50K 任务正在运行,但我的 i7 860 CPU(4 核,8 线程)上的 CPU 使用率约为 50%。

我的想法是使用并行 for 循环来最大化 CPU 使用率并使其更快。这就是我想出的。

public override bool Equals(object obj)
    {
        RushHourPathLengthNode otherNode = (RushHourPathLengthNode)obj;
        bool result = true;

        Parallel.For(0, carCoords.Length, (i, loopState) =>{
            if (!result)
                loopState.Stop();

            if (carCoords[i].x != otherNode.carCoords[i].x || carCoords[i].y != otherNode.carCoords[i].y)
                result = false;
        });

        return result;
    }

在我看来,它会尝试并行运行,并且一旦发现差异,它就会因为 loopState.Stop 而停止工作。这样 CPU 使用率是 90%+,但我的测试用例需要大约 35 秒来计算,我不明白为什么。

我的实现有问题还是我的整个方法有问题?

编辑: carCoords.Length 将是一个介于 2 和 +-100 之间的值。听起来该值太低,无法证明并行执行此操作是合理的。

【问题讨论】:

  • 好吧,您想在发现差异后立即调用 loopState.Stop(),而不是稍后。例如,在结果=假之前;陈述。我不知道您的示例有多有效,但可能是结果始终被缓存并且永远不会到达其他线程。或者如果是,那么稍后。
  • 另外,carCoords.Length 通常有多大?我怀疑您不会从并行执行中获得任何好处。
  • 您需要锁定结果变量
  • @SriramSakthivel 不正确。结果只设置为false,两个线程是否同时执行都没有关系。
  • @JeffreySax 我知道,但没有锁或 volatile 其他线程可能看不到更新的值。值可能被缓存。锁确保内存屏障,因此更新的值将在所有线程中看到。如果错了,请纠正我。

标签: c# .net parallel-processing task-parallel-library


【解决方案1】:

首先,如果您的 for 循环只进行了 100 次迭代,请不要费心将其并行化。对于您在每次迭代中所做的工作,您需要进行数千次迭代才能使其有价值。

假设您有很多次迭代,大幅减速的主要原因是您在每次迭代中只做了一点点工作。在调度任务、调用方法体等方面有很多开销,并且占据了整个执行时间。

您可以通过使用Partinioner.Create(来自System.Collections.Concurrent 命名空间)将范围划分为多个段来解决此问题。这为您提供了一个包含每个范围的开始和结束索引的元组序列。然后,您可以让每个任务在一个范围内迭代,这样效率更高。

其次,由于局部变量是在闭包对象中捕获的,因此有时在方法体内使用变量的本地副本会更快。

所以这就是我们得到的:

    Parallel.ForEach(Partitioner.Create(0, carCoords.Length), (r, loopState) => {
        var c1 = carCoords;
        var c2 = otherNode.carCoords;
        int end = r.Item2;
        for (int i = r.Item1; i < end; ++i) {
            if (loopState.IsStopped)
                return;
            if (c1[i].x != c2[i].x || c1[i].y != c2[i].y) {
                loopState.Stop();
                return;
            }
        }
    });

我不确定每次迭代都检查IsStopped 是否值得。拥有更多分区(使用Partitioner.Create 重载)并将检查放在 for 循环之前可能会更有效。

【讨论】:

    【解决方案2】:

    在你的类上实现IEqutable&lt;RushHourPathLengthNode&gt; 接口以避免强制转换。它会赢得你的秒数。

    如果 carCoords.Length 较小,则并行循环将花费更多时间,因为管理线程和切换的成本很高。

    【讨论】:

    • 是的 carCoords.Length 对于我想要做的事情来说似乎太小了。不过,界面上的要点很好,欢迎任何加速!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    • 2018-11-26
    • 1970-01-01
    • 2021-04-03
    • 1970-01-01
    相关资源
    最近更新 更多