Почему PLINQ медленнее, чем для цикла?

Скажем, у меня есть два метода:

public BigInteger PFactorial(int n)
{
    return Enumerable.Range(1, n)
                     .AsParallel()
                     .Select(i => (BigInteger)i)
                     .Aggregate(BigInteger.One, BigInteger.Multiply);
}

public BigInteger Factorial(int n)
{
    BigInteger result = BigInteger.One;
    for(int i = 1; i <= n; i++)
        result *= i;
    return result;
 }

Были получены следующие результаты:

PFactorial(25000) -> 0,9897 seconds
Factorial(25000) -> 0,9252 seconds

Я понимаю, что у PLINQ есть некоторые накладные расходы из-за установки потоков, но с таким большим n я ожидал, что PLINQ будет быстрее.

Вот еще один результат:

PFactorial(50000) -> 4,91035 seconds
Factorial(50000) -> 4,40056 seconds

Ответ 1

Параллельно с агрегатом невозможно. По крайней мере, я не могу представить это, на мой взгляд. В любом случае, Вы должны сделать распараллеливание возможным, разделив ваш список на куски. Найдите эти результаты. и, наконец, размножать куски. Вот быстрый способ PLinq.

static public BigInteger PFactorial(int n)
{
    var range = Enumerable.Range(1, n).Select(x => (BigInteger) x).AsParallel();
    var lists = range.GroupBy(x => x/(n/Environment.ProcessorCount)).Select(x => x.AsEnumerable());
    var results = lists.Select(x => x.Aggregate(BigInteger.One, BigInteger.Multiply));
    var result = results.Aggregate(BigInteger.One, BigInteger.Multiply);
    return result;
}

Тест

PFactorial(50000) -> 1,41 seconds
Factorial(50000) -> 2,69 seconds

Изменить: Как упоминалось Servy и Chatzigiannakis, если вы не используете семена, он может прекрасно использовать распараллеливание, и вы получите почти такие же результаты, как и выше (немного быстрее).

return Enumerable.Range(1,n).Select(x => (BigInteger)x).AsParallel().Aggregate(BigInteger.Multiply);

Ответ 2

Не предполагайте, что pLinQ всегда быстрее LinQ. Время выполнения PLinQ на основе многих условий

Используйте PLinQ только тогда, когда есть больше элементов и у вас есть запрос на интенсивность процессора. Я предлагаю использовать System.Threading.Thread.Sleep(1) в функции для эмуляции нагрузки процессора как задержки цикла, а затем вызвать эту функцию из LinQ и PlinQ в 10000 раз. Тогда вы можете увидеть разницу. Пожалуйста, найдите образец здесь

Ваша текущая функция Factorial на самом деле не делает какой-либо ЦП интенсивной задачи и причины PLinQ занимает больше времени, поскольку он погрузил запрос в несколько ядер и консолидировал результат отдельного ядра для одиночного вывода, что занимает немного больше времени, чем обычно.

Также убедитесь, что вы используете многоядерные процессоры (минимум 4 даст вам хороший анализ)