Parallel.For против цикла for

Я ломаю голову над запуском параллельных потоков, поэтому решил протестировать его на меньшем количестве, а затем расширить, когда мне будет удобно. Я противопоставляю тот же процесс самому себе; один использует Parallel.For, а другой использует базовый цикл for. Я фиксирую время (в тиках) для сравнения. Пример кода берет массив (в данном случае 53 строки из двух символов) и заполняет заданный ListBox массивом.

Что для меня практически не имеет смысла, так это то, что когда я запускаю базовый цикл For, он дает в среднем 1400 тиков, но когда я запускаю цикл Parallel.For, он возвращает в среднем 5200 тиков. Не слишком ли мал размер выборки для того, чтобы параллель была эффективной?

Вот два фрагмента, которые я использую. Цикл Parallel.For:

public void ListboxFromArray(ListBox listbox, string[] array1)
{
    // This method takes an array and fills a listbox full of items from the array
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();

    Parallel.For(0, array1.Count(),
    index =>
    {
        listbox.Items.Add(array1[index]);
    });
    stopWatch.Stop();
    long ts = stopWatch.ElapsedTicks;
    string elapsedTime = ts.ToString() + " Ticks"; ;
    MessageBox.Show(elapsedTime);

}

и цикл for:

public void ListboxFromArray(ListBox listbox, string[] array1)
{
    // This method takes an array and fills a listbox full of items from the array
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();

    for (int i = 0; i < array1.Count(); i++)
    {
        listbox.Items.Add(array1[i]);
    }
    stopWatch.Stop();
    long ts = stopWatch.ElapsedTicks;
    string elapsedTime = ts.ToString() + " Ticks"; ;
    MessageBox.Show(elapsedTime);
}

Спасибо за любой вклад или подтверждение моих мыслей заранее.


person Darw1n34    schedule 09.03.2016    source источник
comment
В дополнение к времени выполнения вы можете посмотреть, каков окончательный порядок элементов списка. В лучшем случае они будут в случайном полунесовпадающем порядке, не относящемся к массиву. В худшем случае, если список не является потокобезопасным, он будет выполнять неопределенные действия. В дополнение к использованию только для случаев с высокой загрузкой ЦП, вы должны попытаться зарезервировать Parallel для случаев, когда выполнение элемента не полностью зависит от одного центрального объекта. (Это может означать объединение результатов в будущем)   -  person Katana314    schedule 09.03.2016


Ответы (3)


Я думаю, что размер вашей выборки слишком мал, а работа, которую вы выполняете в цикле, слишком мала, чтобы преодолеть накладные расходы на управление задачами/потоками. Нет большого смысла в параллелировании операции в памяти, которая потребляет так мало ЦП. Если бы вы выполняли сортировку 100 000 элементов, то, возможно...

person PhillipH    schedule 09.03.2016
comment
Спасибо, вот что меня тогда гложет. Я не был уверен на 100%, но для меня это тоже имеет смысл, я просто хотел услышать второе мнение. - person Darw1n34; 09.03.2016
comment
На самом деле это неправильно. Размер его выборки не так уж и мал — кроме того, его код не является потокобезопасным. Кроме того, я манипулирую элементом управления пользовательским интерфейсом, который не является многопоточным, поэтому, если он работает, он работает, автоматически распределяя его в ОДИН поток. Совершенно тупой тест. - person TomTom; 09.03.2016
comment
@TomTom - Items - это просто ListBoxObjectCollection, который не является потокобезопасным (коллекции не являются потокобезопасными, если они явно не предназначены для этого), но это не влияет на производительность его теста. Согласен, что результат добавления может быть не в каком-либо определенном порядке (но это ожидается от Parallel.For), и это не реальный пример, но поскольку он не получает исключения, его доступ к коллекции разрешен - если только вы думаете, что Parallel.ForEach замечает перекрестный доступ к элементу управления, а затем один поток .... что я не думаю, что это вероятно. Плохой тест, но... - person PhillipH; 10.03.2016

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

int[] coll = new int[]{10,9,8,7,6,5,4,3,2,1};

Parallel.ForEach(coll,
    item=>
    {
       Thread.Sleep(TimeSpan.FromSeconds(item));
       Console.WriteLine(item + "Finished");
    });

по сравнению с последовательным способом

foreach (var item in coll)
{
    Thread.Sleep(TimeSpan.FromSeconds(item));
    Console.WriteLine(item + "Finished");
}

первый код работает быстрее, потому что он действительно выполняет работу параллельно.

В вашем случае, если вы просто хотите просто «не работать», то накладные расходы в thead просто слишком велики.

person Michael Mairegger    schedule 09.03.2016

Я не думаю, что вы можете это сделать... во-первых, вы получаете доступ к элементу управления пользовательского интерфейса (ListBox) из потока, отличного от пользовательского интерфейса. Насколько я знаю, Parallel.For не выполняет сортировку потоков, чтобы сделать это безопасным. Во-вторых, вы обращаетесь к одной и той же коллекции (ListBox.Items) из нескольких потоков без блокировки — опять же, небезопасно.

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

person Mark Brackett    schedule 09.03.2016
comment
На самом деле я не согласен с вами по поводу использования, я больше просил собственного назидания в моем понимании процессов, связанных друг с другом. Спасибо. - person Darw1n34; 09.03.2016