2013-08-13 4 views
2

Этот код из статьи Microsoft http://msdn.microsoft.com/en-us/library/dd460703.aspx, с небольшими изменениями:Parallel.For производительность

 const int size = 10000000; 
     int[] nums = new int[size]; 
     Parallel.For(0, size, i => {nums[i] = 1;}); 
     long total = 0; 

     Parallel.For<long>(
      0, size,() => 0, 
      (j, loop, subtotal) => 
      { 
       return subtotal + nums[j]; 
      }, 
      (x) => Interlocked.Add(ref total, x) 
     ); 

     if (total != size) 
     { 
      Console.WriteLine("Error"); 
     } 

Non-параллельная версия петли:

 for (int i = 0; i < size; ++i) 
     { 
      total += nums[i]; 
     } 

Когда я измеряю время выполнения цикла, используя StopWatch класс, Я вижу, что параллельная версия медленнее на 10-20%. Тестирование выполняется на 64-разрядной версии Windows 7, процессоре Intel i5-2400, 4 ядрах, 4 ГБ оперативной памяти. Конечно, в конфигурации Release.

В моей реальной программе я пытаюсь вычислить гистограмму изображения, а параллельная версия работает в 10 раз медленнее. Могут ли такие задачи вычислений, когда каждый вызов цикла выполняется очень быстро, успешно распараллеливаются с TPL?

Редактировать.

Наконец-то мне удалось побрить больше, чем 50% времени выполнения расчета гистограммы с помощью Parallel.For, когда разделили все изображение на некоторое количество кусков. Каждый вызов цикла цикла обрабатывает весь фрагмент, а не один пиксель.

+0

Также имейте в виду, что Parallel.For максимизирует 64-бит. Я тестировал его в прошлом с программой, в которой Parallel.Loop обеспечивал хорошую производительность (как объясняется в ответе ниже) и заметил заметное улучшение при нацеливании проекта на x64. В этой конкретной конфигурации он был примерно в 3-4 раза быстрее (в версии x86 Parallel.Loop был уже в 5 раз быстрее, чем обычный цикл). – varocarbas

+0

Просто для уточнения точных условий в случае моего предыдущего комментария: итеративные вычисления/хранение занимает довольно много времени. В своей первоначальной версии это заняло около 1 часа; который был сокращен примерно до 20 минут с параллельным контуром на x86 и до 5 минут с версией с 64-битным титром. Для более простых конфигураций (циклов, занимающих всего миллисекунды), довольно сложно заметно повысить производительность простого цикла. – varocarbas

ответ

4

Потому что Parallel.For следует использовать для вещей, которые являются немногочисленными, а не простоты простых чисел! Просто использование делегата (j, loop, subtotal) =>, вероятно, более чем достаточно, чтобы дать на 10-20% больше времени. И мы даже не говорим о накладных расходах. Было бы интересно увидеть некоторый бенчмарк против лета делегата в цикле и посмотреть не только время реального мира, но и время процессора.

Я даже добавил сравнение с «простым» делегатом, который делает то же самое, что и делегат Parallel.For<>.

Мммм ... Теперь у меня есть некоторые цифры на 32 бит, на моем компьютере (AMD шесть ядро)

32 bits 
Parallel: Ticks:  74581, Total ProcessTime: 2496016 
Base : Ticks:  90395, Total ProcessTime:  312002 
Func : Ticks:  147037, Total ProcessTime:  468003 

Параллельная немного быстрее во время стены, но 8x медленнее процессорного времени: -)

Но на 64 бита:

64 bits 
Parallel: Ticks:  104326, Total ProcessTime: 2652017 
Base : Ticks:  51664, Total ProcessTime:  156001 
Func : Ticks:  77861, Total ProcessTime:  312002 

Модифицированный код:

Console.WriteLine("{0} bits", IntPtr.Size == 4 ? 32 : 64); 

var cp = Process.GetCurrentProcess(); 
cp.PriorityClass = ProcessPriorityClass.High; 

const int size = 10000000; 
int[] nums = new int[size]; 
Parallel.For(0, size, i => { nums[i] = 1; }); 

GC.Collect(); 
GC.WaitForPendingFinalizers(); 

long total = 0; 

{ 
    TimeSpan start = cp.TotalProcessorTime; 
    Stopwatch sw = Stopwatch.StartNew(); 

    Parallel.For<long>(
     0, size,() => 0, 
     (j, loop, subtotal) => 
     { 
      return subtotal + nums[j]; 
     }, 
     (x) => Interlocked.Add(ref total, x) 
    ); 

    sw.Stop(); 
    TimeSpan end = cp.TotalProcessorTime; 

    Console.WriteLine("Parallel: Ticks: {0,10}, Total ProcessTime: {1,10}", sw.ElapsedTicks, (end - start).Ticks); 
} 

if (total != size) 
{ 
    Console.WriteLine("Error"); 
} 

GC.Collect(); 
GC.WaitForPendingFinalizers(); 

total = 0; 

{ 
    TimeSpan start = cp.TotalProcessorTime; 
    Stopwatch sw = Stopwatch.StartNew(); 

    for (int i = 0; i < size; ++i) 
    { 
     total += nums[i]; 
    } 

    sw.Stop(); 
    TimeSpan end = cp.TotalProcessorTime; 

    Console.WriteLine("Base : Ticks: {0,10}, Total ProcessTime: {1,10}", sw.ElapsedTicks, (end - start).Ticks); 
} 

if (total != size) 
{ 
    Console.WriteLine("Error"); 
} 

GC.Collect(); 
GC.WaitForPendingFinalizers(); 

total = 0; 

Func<int, int, long, long> adder = (j, loop, subtotal) => 
{ 
    return subtotal + nums[j]; 
}; 

{ 
    TimeSpan start = cp.TotalProcessorTime; 
    Stopwatch sw = Stopwatch.StartNew(); 

    for (int i = 0; i < size; ++i) 
    { 
     total = adder(i, 0, total); 
    } 

    sw.Stop(); 
    TimeSpan end = cp.TotalProcessorTime; 

    Console.WriteLine("Func : Ticks: {0,10}, Total ProcessTime: {1,10}", sw.ElapsedTicks, (end - start).Ticks); 
} 

if (total != size) 
{ 
    Console.WriteLine("Error"); 
} 
+0

+1 хорошо суммировано – Ehsan

+0

Итак, TPL недостаточно хорош для таких простых задач. В native C++ я могу использовать OpenMP. Есть ли что-нибудь в .NET, которое подходит для таких задач вычисления? –

+0

@AlexFarber Мне еще предстоит найти хорошее применение для 'Parallel.For <>' :-) Количество хороших вариантов использования очень мало. Возможно, загрузка нескольких веб-страниц ... – xanatos

Смежные вопросы