Поделиться через


Практическое руководство. Повышение скорости выполнения небольших тел циклов

Цикл For() с небольшим телом может выполняться медленнее, чем эквивалентный последовательный цикл. Снижение производительности вызвано большой затратой ресурсов при секционировании данных и вызове делегата в каждой итерации цикла. Для работы с такими сценариями класс Partitioner предоставляет метод Create, позволяющий выполнять последовательный цикл для тела делегата, чтобы делегат вызывался только один раз для каждой части, а не для каждой итерации. Дополнительные сведения см. в разделе Пользовательские разделители для PLINQ и TPL.

Пример

Imports System.Threading.Tasks
Imports System.Collections.Concurrent

Module PartitionDemo

    Sub Main()
        ' Source must be array or IList.
        Dim source = Enumerable.Range(0, 100000).ToArray()

        ' Partition the entire source array. 
        ' Let the partitioner size the ranges.
        Dim rangePartitioner = Partitioner.Create(0, source.Length)

        Dim results(source.Length - 1) As Double

        ' Loop over the partitions in parallel. The Sub is invoked
        ' once per partition.
        Parallel.ForEach(rangePartitioner, Sub(range, loopState)

                                               ' Loop over each range element without a delegate invocation.
                                               For i As Integer = range.Item1 To range.Item2 - 1
                                                   results(i) = source(i) * Math.PI
                                               Next
                                           End Sub)
        Console.WriteLine("Operation complete. Print results? y/n")
        Dim input As Char = Console.ReadKey().KeyChar
        If input = "y"c Or input = "Y"c Then
            For Each d As Double In results
                Console.Write("{0} ", d)
            Next
        End If

    End Sub
End Module
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {

        // Source must be array or IList.
        var source = Enumerable.Range(0, 100000).ToArray();

        // Partition the entire source array.
        var rangePartitioner = Partitioner.Create(0, source.Length);

        double[] results = new double[source.Length];

        // Loop over the partitions in parallel.
        Parallel.ForEach(rangePartitioner, (range, loopState) =>
        {
            // Loop over each range element without a delegate invocation.
            for (int i = range.Item1; i < range.Item2; i++)
            {
                results[i] = source[i] * Math.PI;
            }
        });

        Console.WriteLine("Operation complete. Print results? y/n");
        char input = Console.ReadKey().KeyChar;
        if (input == 'y' || input == 'Y')
        {
            foreach(double d in results)
            {
                Console.Write("{0} ", d);
            }           
        }
    }
}

Подход, продемонстрированный в этом примере, полезен, если цикл выполняет минимальный объем работы. Если работа требует больших затрат вычислительных ресурсов, вероятно, можно добиться той же производительности или даже повысить ее, используя цикл For или ForEach с модулем разделения по умолчанию.

См. также

Ссылки

Итераторы (Руководство по программированию в C#)

Основные понятия

Параллелизм данных (библиотека параллельных задач)

Пользовательские разделители для PLINQ и TPL

Лямбда-выражения в PLINQ и библиотеке параллельных задач