C # Parallel Vs. Производительность многопоточного кода

Я тестировал производительность System.Threading.Parallel против Threading, и я удивлен, что Parallel занимает больше времени для завершения задач, чем многопоточность. Я уверен, что это из-за моего ограниченного знания Parallel, о котором я только начал читать.

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

public class ThreadFactory
{
    int workersCount;
    private List<Thread> threads = new List<Thread>();

    public ThreadFactory(int threadCount, int workCount, Action<int, int, string> action)
    {
        workersCount = threadCount;

        int totalWorkLoad = workCount;
        int workLoad = totalWorkLoad / workersCount;
        int extraLoad = totalWorkLoad % workersCount;

        for (int i = 0; i < workersCount; i++)
        {
            int min, max;
            if (i < (workersCount - 1))
            {
                min = (i * workLoad);
                max = ((i * workLoad) + workLoad - 1);
            }
            else
            {
                min = (i * workLoad);
                max = (i * workLoad) + (workLoad - 1 + extraLoad);
            }
            string name = "Working Thread#" + i; 

            Thread worker = new Thread(() => { action(min, max, name); });
            worker.Name = name;
            threads.Add(worker);
        }
    }

    public void StartWorking()
    {
        foreach (Thread thread in threads)
        {
            thread.Start();
        }

        foreach (Thread thread in threads)
        {
            thread.Join();
        }
    }
}

Вот программа:

Stopwatch watch = new Stopwatch();
watch.Start();
int path = 1;

List<int> numbers = new List<int>(Enumerable.Range(0, 10000));

if (path == 1)
{
    Parallel.ForEach(numbers, x =>
    {
        Console.WriteLine(x);
        Thread.Sleep(1);

    });
}
else
{
    ThreadFactory workers = new ThreadFactory(10, numbers.Count, (min, max, text) => {

        for (int i = min; i <= max; i++)
        {
            Console.WriteLine(numbers[i]);
            Thread.Sleep(1);
        }
    });

    workers.StartWorking();
}

watch.Stop();
Console.WriteLine(watch.Elapsed.TotalSeconds.ToString());

Console.ReadLine();

Обновление:

Принимая во внимание блокировку: я попробовал следующий фрагмент. Снова те же результаты, Параллель, кажется, заканчивается гораздо медленнее.

path = 1; cieling = 10000000;

    List<int> numbers = new List<int>();

    if (path == 1)
    {
        Parallel.For(0, cieling, x =>
        {
            lock (numbers)
            {
                numbers.Add(x);    
            }

        });
    }

    else
    {
        ThreadFactory workers = new ThreadFactory(10, cieling, (min, max, text) =>
        {

            for (int i = min; i <= max; i++)
            {
                lock (numbers)
                {
                    numbers.Add(i);    
                }                       

            }
        });

        workers.StartWorking();
    }

Обновление 2: Просто быстрое обновление, что моя машина имеет четырехъядерный процессор. Так что у Parallel есть 4 ядра.

10
задан ace 26 August 2010 в 17:54
поделиться

3 ответа

Думаю, я могу ответить на ваш вопрос. Во-первых, вы не написали, сколько ядер у вашей системы.если вы используете двухъядерный процессор, только 4 потока будут работать с использованием Parallel.For, в то время как вы работаете с 10 потоками в своем примере Thread. Больше потоков будет работать лучше, поскольку выполняемая вами задача (печать + короткий сон) является очень короткой задачей для потоковой передачи, а накладные расходы потока очень велики по сравнению с задачей, я почти уверен, что если вы напишете тот же код без потоков это будет работать быстрее.

Оба ваших метода работают почти одинаково, но если вы создадите все потоки заранее, вы сэкономите много, так как Parallel.For использует пул задач, что добавляет некоторые накладные расходы на перемещение.

3
ответ дан 4 December 2019 в 03:14
поделиться

Ссылаясь на сообщение в блоге Рида Копси-младшего:

Parallel.ForEach немного сложнее. При работе с универсальным IEnumerable количество элементов, необходимых для обработки, заранее неизвестно и должно быть обнаружено во время выполнения. Кроме того, поскольку у нас нет прямого доступа к каждому элементу, планировщик должен перечислить коллекцию для ее обработки. Поскольку IEnumerable не является потокобезопасным, он должен блокировать элементы по мере их перечисления, создавать временные коллекции для каждого обрабатываемого фрагмента и планировать это.

Блокировка и копирование могут увеличить время работы Parallel.ForEach. Кроме того, разделение и планировщик ForEach могут повлиять и привести к накладным расходам. Я протестировал ваш код и увеличил сон каждой задачи, и тогда результаты ближе, но все равно ForEach медленнее.

[Правка – дополнительные исследования]

Я добавил в циклы выполнения следующее:

if (Thread.CurrentThread.ManagedThreadId > maxThreadId)
   maxThreadId = Thread.CurrentThread.ManagedThreadId;

Это показывает, что на моей машине с ForEach используется на 10 потоков меньше, чем на текущие настройки. Если вы хотите больше потоков из ForEach, вам придется возиться с ParallelOptions и планировщиком.

См. Ограничивает ли Parallel.ForEach количество активных потоков?

3
ответ дан 4 December 2019 в 03:14
поделиться

Это логично :-)

Это был бы первый случай в истории, когда добавление одного (или двух) слоев кода повысило производительность. Когда вы используете удобные библиотеки, вы должны быть готовы заплатить цену. Кстати, вы не опубликовали цифры. Нужно опубликовать результаты :-)

Чтобы сделать результаты более неудачными (или предвзятыми :-) для Parallel-ов, преобразуйте список в массив.

Затем, чтобы сделать их совершенно несправедливыми, разделите работу по своему усмотрению, создайте массив всего из 10 элементов и полностью передайте действия Parallel. Вы, конечно, выполняете работу, которую Parallel-s обещал сделать для вас на данный момент, но это должно быть интересное число :-)

Кстати, я только что прочитал блог Рида. Разбиение, используемое в этом вопросе, он называет самым простым и наивным разбиением. Что делает его действительно очень хорошим тестом на выбывание. Вам все равно нужно проверить случай с нулевой работой, чтобы узнать, полностью ли он забит.

0
ответ дан 4 December 2019 в 03:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: