【问题标题】:How I can change a For loop to PLINQ syntax?如何将 For 循环更改为 PLINQ 语法?
【发布时间】:2014-07-04 05:47:55
【问题描述】:

我的代码有一个真正的问题,它非常慢并且毫无用处。我必须在下周提供我的结果,而按照这个代码,它需要超过 4 周的时间! 由于此代码计算流体运动,我们处理超过 100 万个细胞。因此,我的主要 For 循环(顺序很重要)计算量很大,现在每个输出都需要 12 分钟。 想象一下,我需要至少 5000 个输出!

谁能帮我提高计算速度? 或者 您能帮我理解如何将 For 循环更改为 PLINQ 语法吗?

我很沮丧

//implementation of time nt = 1,000,000
    For (int t=0; t<nt; t++)
    { 
// main calculations, 2 dimensional   
    for (int i = 0; i < nx; i++)
        {
            for (int j = 0; j < ny; j++)
            {
                if (!Cells[i, j, 0].IsVirtual)
                {
                    // calculate x velocity - nx = 1,000,000 & ny = 1,000,000
                    // calculate y velocity
                    // Data for each (i,j) affect (i+1,j+1)
            }
         }
     }
    }

【问题讨论】:

  • 第一个问题将是计算可以并行完成,正如您提到的顺序很重要,那么一次运行循环会影响下一次运行吗?还有大部分时间花在哪里,实际计算或者您是否访问对象来进行计算
  • 代码非常大,但最多的计算是在两个 For 循环中,并且对于每个输出(它有一个更大的 For 循环,一次工作)这两个内部 For 循环应该完成。这些 For 循环计算 100 万个点的速度。其他 For 循环对速度没有影响。
  • 您是否有从分析当前实现的热门路径?使用并行处理可以给您带来边际加速,但优化实际实现可能会产生额外的改进。
  • 您确定每个内部部件都独立于其他内部部件吗?
  • 我同意@GregC,也许你的算法是问题,而不是实现。您是否研究过解决问题的数值方法?

标签: c# plinq


【解决方案1】:

为了能够使用 PLINQ,您需要将二维 for 循环更改为可以处理的元素序列(例如 IEnumerable&lt;T&gt;)。您可以使用以下代码将 x 和 y 值创建为序列:

var indices = Enumerable.Range(0, nx).SelectMany(
  _ => Enumerable.Range(0, ny),
 (x, y) => new { X = x, Y = y }
);

然后您可以使用Where 过滤索引序列:

indices.Where(index => !Cells[index.X, index.Y, 0].IsVirtual)

然后您可以使用Select 投影索引以创建结果:

var results = indices
  .Where(...)
  .Select(index => new { index.X, index.Y, Result = ... });

当您将代码转换为这种形式并确保它适用于小型数据集后,您可以使用 PLINQ 通过插入 AsParallel 并行运行 Select 中的计算:

var results = indices
  .Where(...)
  .AsParallel()
  .Select(index => new { index.X, index.Y, Result = ... })
  .ToList();

在您的问题中,您谈到了秩序的重要性。您可以使用AsOrdered 确保生成的结果与输入索引保持相同的顺序:

var results = indices
  .Where(...)
  .AsParallel()
  .AsOrdered()
  .Select(index => new { index.X, index.Y, Result = ... })
  .ToList();

但是,您的 for 循环的“主体”仍然以没有特定顺序的并行执行。只有在ToList 创建列表时才排序的最终结果。如果您提到的顺序的重要性意味着您在 for 循环的主体中使用了可变状态,那么您将无法使用 PLINQ。

这种使用 PLINQ 的方法并不是特别有效。如果您必须处理 1,000,000 个单元格,则此代码将分配 1,000,000 个对象来存储单元格的索引。但是,似乎是处理时间而不是内存分配和垃圾收集限制了您的计算。

如果您使用 Parallel.For 之类的东西来并行化您的计算,则可以避免分配的开销,但是您将使用 TPL 而不是 PLINQ。

【讨论】:

  • 感谢您的回答。 For 循环应该是有序的,因为每次迭代的结果对于下一次迭代都是必要的。所以他们应该一个一个地完成。这样我就不能使用 TPL 和 PLINQ。那么我应该怎么做才能提高速度呢?
  • @user3803849:很难说,因为我没有关于你的计算的信息。要并行化您的计算,您需要在逻辑上找到一种对工作进行分区的方法,以便多个线程可以并行执行。 TPL 和 PLINQ 只有在您想出一种划分工作的方法时才能帮助您完成工作。它不能为你神奇地想出这个分区。如果您意识到您的算法要求以严格的顺序处理单元格,您可能会专注于优化 for 循环内的计算。
  • 我的计算方式是,例如 (i,j) 处的速度需要计算 (i+1,j+1) 处的速度。所以一切都应该井井有条。我的代码需要时间,因为它需要计算很多点和很多时间。例如,对于具有 100 万个单元(二维单元)的系统,至少有 1000 万次。如何优化这种计算?我的一些用 C++ 编写的朋友使用 GPU 实现来加速他们的计算。
  • 如果您的要求是按顺序计算行,您可以对该循环内的行使用正常的 for 循环,您可以使用 TPL 或 PLINQ 对单元格内的单元格进行“并行”循环排。因为一行中的单元数要多得多,所以您仍然可以最大限度地利用内核。
【解决方案2】:

使用下面的代码,您可以使用您的收藏而不是列表。如果您需要任何帮助,请告诉我:

List<string> lst = new List<string>();
foreach (var item in lst.AsParallel())
{
    foreach (var data in item)
    {

    }
}

【讨论】:

  • 嗨,其实我听不懂你的评论
  • 你的代码中的“nx”是什么,或者给我整个代码,我会把它转换成 plinq。
  • 我的代码中速度最慢的主要部分是上面提到的两个 For 循环。 nx= 1000,000 - ny=1000,000,在 For 循环中有很大一部分是速度计算。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-13
  • 2017-09-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多