2012-11-19 2 views
3

Пусть у меня есть ряд Particle с в X, Y пространство, и я хочу, чтобы нормализовать их все так, чтобы среднее X и Y равны 0.PLINQ AsParallel(). ForAll() доступа ресурс

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

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 (п), но это «ошеломляюще параллельно». Посмотрите на мою реализацию 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, потому что они не объекты, но я не уверен, что я бы хотел, так или иначе, потому что не блокировал довольно дорого?

+0

Нет такой вещи, как «довольно атомный», операция либо атомарна, либо нет. – svick

ответ

5

Вы можете обойти необходимость блокировки (или атомарных операций) с помощью обычного механизма параллельного 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) операции, я был бы очень удивлен, если эта часть занимала значительную часть времени.

+0

Я люблю LINQ все больше и больше каждый день ... – Ozzah

1

Использование

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

сделать поточно-атомное дополнение. Для получения дополнительной информации см. Документацию Interlocked Class.

+0

Лучшим решением может быть параллельная сумма, которая автоматически обрабатывает это для вас. –

+1

'Interlocked.Add' только перегружает' (ref int, int) 'и' (ref long, long) ', но я использую' double' – Ozzah

0

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

+0

I не знаю, выполняет ли это JIT-complier, но очень возможно, что он может изменить инструкции для [SIMD-инструкций] (http://en.wikipedia.org/wiki/SIMD) через [расширения процессора SSE] (http : //en.wikipedia.org/wiki/Streaming_SIMD_Extensions). [Вот блог] (http://blogs.msdn.com/b/davidnotario/archive/2005/08/15/451845.aspx) с дополнительной информацией о CLR и расширениях, таких как SSE –

+0

Да, но алгоритм по-прежнему привязан к памяти, поэтому процессор по-прежнему в основном бездействует, но немногие работы, которые он выполняет, теперь равномерно распределены между его ядрами. –

+0

@MathiasBecher: O (N) проблемы, подобные этому, на самом деле очень хороши с параллелизмом. Если размер вашей проблемы растет, вы можете, по большей части, бросить на нее больше аппаратных средств для выполнения на том же уровне, что и раньше. Я согласен с тем, что это может быть немного тривиально, если у вас есть что-то вроде N = 10^7. –