PLINQ AsParallel ().ForAll () доступ к ресурсу


Предположим, что у меня есть некоторое число Particles в пространстве X,Y, и я хочу нормализовать их все так, чтобы средние X и Y равнялись 0.

Последовательная реализация:

public void Normalise()
{
  double avgX = 0.0;
  double avgY = 0.0;

  foreach (Particle p in Particles)
  {
    avgX += p.X;
    avgY += p.Y;
  }

  avgX /= (double)Particles.Count;
  avgY /= (double)Particles.Count;

  foreach (Particle p in Particles)
  {
    p.X -= avgX;
    p.Y -= avgY;
  }
}

Это работает, и производительность неплохая, так как это O(n), но это "смущающе параллельно". Взгляните на мою реализацию PLINQ:

public void PNormalise()
{
  double avgX = 0.0;
  double avgY = 0.0;

  Particles.AsParallel().ForAll(p =>
  {
    avgX += p.X;
    avgY += p.Y;
  });

  avgX /= (double)Particles.Count;
  avgY /= (double)Particles.Count;

  Particles.AsParallel().ForAll(p =>
  {
    p.X -= avgX;
    p.Y -= avgY;
  });
}

Я не уверен насчет исполнения здесь, но я бы предположил, что это лучше. Проблема в том, что все частицы скачут вокруг хаотично. Я могу только предположить, что += операции над avgX и avgY конкурируют друг с другом, хотя они уже достаточно атомарны.

Есть ли что-нибудь, что я могу сделать, чтобы исправить это? Я не могу lock их, потому что они не являются объектами, но я не уверен, что я хотел бы в любом случае, потому что замок не довольно дорого?
3 3

3 ответа:

Вы можете обойти необходимость блокировки (или атомарных операций) с помощью обычного механизма параллельного LINQ:

var avgX = Particles.AsParallel().Average(p => p.X);
var avgY = Particles.AsParallel().Average(p => p.Y);

Particles.AsParallel().ForAll(p => { p.X -= avgX; p.Y -= avgY });
Поскольку суммирование чисел является операцией O (N), я был бы крайне удивлен, если бы эта часть заняла сколько-нибудь значительную часть времени.

Использовать

Particles.AsParallel().ForAll(p =>
{
    Interlocked.Add(avgX, p.X);
    Interlocked.Add(avgY, p.Y);
}

Для выполнения потокобезопасного атомарного сложения. Дополнительную информацию смотрите в документации по блокируемому классу .

На самом деле, распараллеливание этого O(n)-алгоритма не приведет к намного лучшей производительности, так как у вас есть примерно такой же объем доступа к памяти, как и вычислительные инструкции.