Это ошибка PLINQ? - PullRequest
       32

Это ошибка PLINQ?

2 голосов
/ 18 февраля 2011

Почему выход PLINQ отличается от последовательной обработки и Parallel.For loop

Я хочу добавить сумму квадратного корня из 10 000 000 чисел. Вот код для 3 случаев:

последовательно для цикла:

double sum = 0.0;
for(int i = 1;i<10000001;i++)
sum += Math.Sqrt(i);

Вывод: 21081852648.717

Теперь используется цикл Parallel.For:

object locker = new object();
double total ;

Parallel.For(1,10000001,
()=>0.0,
(i,state,local)=> local+Math.Sqrt(i),
(local)=>
{
  lock(locker){ total += local; }
}
);

Вывод: 21081852648.7199

Теперь используется PLINQ

double tot =  ParallelEnumerable.Range(1, 10000000)
                .Sum(i => Math.Sqrt(i)); 

Вывод этого: 21081852648.72

Почему существует разница между выходом PLINQ и Parallel.For и последовательным для цикла?

1 Ответ

5 голосов
/ 18 февраля 2011

Я сильно подозреваю, что это потому, что арифметика с двойными числами не является действительно ассоциативной. Информация может быть потеряна при суммировании значений, и в точности какая информация будет потеряна, будет зависеть от порядка операций.

Вот пример, показывающий этот эффект:

using System;

class Test
{
    static void Main()
    {
        double d1 = 0d;
        for (int i = 0; i < 10000; i++)
        {
            d1 += 0.00000000000000001;
        }
        d1 += 1;
        Console.WriteLine(d1);

        double d2 = 1d;
        for (int i = 0; i < 10000; i++)
        {
            d2 += 0.00000000000000001;
        }
        Console.WriteLine(d2);
    }
}

В первом случае мы можем добавлять очень маленькие числа много раз, пока они не станут достаточно большими, чтобы оставаться актуальными при добавлении в 1.

Во втором случае добавление 0,00000000000000001 к 1 всегда приводит только к 1, так как в двойном коде недостаточно информации для представления 1.00000000000000001 - поэтому конечный результат все еще равен 1.

РЕДАКТИРОВАТЬ: Я думал о другом аспекте, который может сбить с толку. Для локальных переменных JIT-компилятор может (и может) использовать 80-битные регистры FP, что означает, что арифметика может выполняться с меньшими потерями информации. Это не случай переменных экземпляра, которые обязательно должны быть 64-битными. В вашем случае Parallel.For переменная total будет фактически переменной экземпляра в сгенерированном классе, потому что она захвачена лямбда-выражением. может изменить результаты, но вполне может зависеть от архитектуры компьютера, версии CLR и т. Д.

...