С# Параллельный и последовательный

У меня есть большой список для просмотра (1 500 000 элементов), с каждым элементом я должен сделать очень маленькую проверку. Всего за 30 сек.

Загрузка ЦП при использовании Sequential составляет около 10%, поэтому многие ресурсы не используются.

Первой мыслью было использовать Parallel, но из-за ограниченной продолжительности времени для каждого элемента Parallel длится дольше, чем последовательный Foreach, это связано с "Почему в этом примере параллельная версия была медленнее, чем последовательная?", что объясняет, что создание каждой задачи будет стоить времени.

Поэтому у меня была другая мысль: разделить список на 4 (или более) равных части и создать поток для циклического перебора элементов, чтобы получить его быстрее.

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

Код

Код, который я создал для другого параллельного подхода: (используется в моем собственном статическом классе)

public static void ForEach<T>(IEnumerable<T> list, Action<T> body, int listDevide)
{
    // Number of items
    int items = list.Count();
    // Divided (in int, so floored)
    int listPart = items / listDevide;
    // Get numbers extra for last run
    int rest = items % listDevide;

    // List to save the actions
    var actions = new List<Action>();
    for(var x = 0; x < listDevide; x++)
    {
        // Create the actions
        actions.Add(delegate {
            foreach(var item in list.Skip(x * listPart).Take(listPart))
            {
                body.Invoke(item);
            }
        });
    }

    // Run the actions parallel
    Parallel.Invoke(actions.ToArray());
}

Примечание: переменная rest для выполнения последних элементов в этом примере в настоящее время не используется.

Решение ниже, дополнительная информация: http://msdn.microsoft.com/en-us/library/dd997411.aspx


person user2331234    schedule 04.07.2013    source источник
comment
СДЕЛАЙТЕ два потока и посмотрите, загружается ли процессор на 20% ...   -  person Bart Friederichs    schedule 04.07.2013
comment
Что вы получили, используя второй подход? лучшая производительность?   -  person Shaharyar    schedule 04.07.2013
comment
Когда время вычисления 1 элемента очень мало, лучше использовать разделитель, чтобы обрабатывать список небольшими порциями в потоке. Использование только параллельного цикла стоило бы мне больше времени, чем использование последовательного цикла. Использование разделителя дает прирост производительности (это быстрее, чем последовательный цикл).   -  person user2331234    schedule 09.07.2013


Ответы (1)


Да, разделение входного массива — хороший подход.

На самом деле Microsoft предоставляет класс Partitioner, помогающий именно этому подходу.

Вот пример, показывающий, как это сделать:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Demo
{
    class Program
    {
        private void run()
        {
            double sum = 0;
            Func<double, double> func = x => Math.Sqrt(Math.Sin(x));
            object locker = new object();

            double[] data = testData();

            // For each double in data[] we are going to calculate Math.Sqrt(Math.Sin(x)) and
            // add all the results together.
            //
            // To do this, we use class Partitioner to split the input array into just a few partitions,
            // (the Partitioner will use knowledge about the number of processor cores to optimize this)
            // and then add up all the values using a separate thread for each partition.
            //
            // We use threadLocalState to compute the total for each partition, and then we have to
            // add all these together to get the final sum. We must lock the additon because it isn't
            // threadsafe, and several threads could be doing it at the same time.

            Parallel.ForEach
            (
                Partitioner.Create(0, data.Length),

                () => 0.0,

                (subRange, loopState, threadLocalState) =>
                {
                    for (int i = subRange.Item1; i < subRange.Item2; i++)
                    {
                        threadLocalState += func(data[i]);
                    }

                    return threadLocalState;
                },

                finalThreadLocalState =>
                {
                    lock (locker)
                    {
                        sum += finalThreadLocalState;
                    }
                }
            );

            Console.WriteLine("Sum = " + sum);
        }

        private static double[] testData()
        {
            double[] array = new double[1000003]; // Test with an odd number of values.

            Random rng = new Random(12345);

            for (int i = 0; i < array.Length; ++i)
                array[i] = rng.Next() & 3; // Don't want large values for this simple test.

            return array;
        }

        static void Main()
        {
            new Program().run();
        }
    }
}
person Matthew Watson    schedule 04.07.2013
comment
Спасибо, это то, что мне было нужно! - person user2331234; 04.07.2013