修改基础列表时PLINQ O(n ^ 2)

时间:2012-11-19 01:31:45

标签: c# multithreading linq plinq

假设我有这个顺序功能:

private void Process()
{
  for (int i = 0; i < Particles.Count; i++)
    for (int j = 0; j < Particles.Count; j++)
      if (check(Particles[i], Particles[j])
      {
        Particle newParticle = Particle.Merge(Particles[i], Particles[j]);
        Particle p1 = Particles[i];
        Particle p2 = Particles[j];

        Particles.Remove(p1);
        Particles.Remove(p2);
        Particles.Add(newParticle);

        i = j = 0;
      }
}

这是做什么的?它会检查是否应该合并两个粒子。如果它们应该,则创建一个新粒子,从列表中删除原始粒子,并将新粒子添加到列表中。

然后我通过将i和j设置为零来做一些懒惰的事情。在一个for循环中,我只能Particles.RemoveAt(i--)并且循环将在它停止的地方继续,但是从这里我们有i和j它更复杂。

反正。这是另一个代码块,应该很容易并行化。唯一的问题是我需要修改我正在迭代的集合,无论它是并行还是顺序。

如果我使用foreach代替for,我会收到一个例外,说明集合的大小已经改变。如果我使用PLINQ:

private void Process()
{
  Particles.AsParallel.ForAll(p1 =>
  {
    Particles.ForEach(p2 =>
    {
      if (check(p1, p2)
      {
        Particle newParticle = Particle.Merge(p1, p2);

        Particles.Remove(p1);
        Particles.Remove(p2);
        Particles.Add(newParticle);
      }
    });
  });
}

我得到一个LINQ例外。

有什么方法可以将这个n ^ 2操作并行化并能够更改列表的内容吗?

1 个答案:

答案 0 :(得分:1)

尝试这样做:

from p1 in Particles.AsParallel()
    let collisions = from p2 in Particles
                     where p1 != p2
                     where check(p1, p2)
                     select p2
    select Particle.Merge(p1, collisions)

第二个Particle.Merge作用于碰撞列表以生成新粒子。你需要更多的逻辑,但这应该给你一个想法。

基本思路是你想要非破坏性地创建列表的新副本。然后做任何修改并替换旧列表。

您要做的一些事情是:

  • 修改Particle.Merge以对列表进行操作:Particle.Merge(Particle p, IEnumerable<Particle> collisions)
  • 添加一些逻辑以防止重复出现在该列表中两次。