Производительность OpenMP

Во-первых, я знаю, что этот [тип] вопроса часто задают, поэтому позвольте мне предварить его, сказав, что я прочитал столько, сколько мог, и я все еще не знаю. знать, в чем дело.

Я распараллелил массивныйвнешний цикл for. Количество итераций цикла варьируется, обычно от 20 до 150, но тело цикла выполняет огромный объем работы, вызывая множество локальных интенсивных подпрограмм линейной алгебры (например, код является частью исходного кода, а не внешней зависимостью). . В теле цикла есть более 1000 вызовов этих подпрограмм, но все они полностью независимы друг от друга, поэтому я решил, что это будет главный кандидат на параллелизм.Код цикла написан на C++, но он вызывает множество подпрограмм, написанных на C.

Код выглядит следующим образом;

<declare and initialize shared variables here>
#ifdef _OPENMP
#pragma omp parallel for                            \
  private(....)\
  shared(....)              \
  firstprivate(....) schedule(runtime)
#endif
  for(tst = 0; tst < ntest; tst++) {

     // Lots of functionality (science!)
     // Calls to other deep functions which manipulate private variables only
     // Call to function which has 1000 loop iterations doing matrix manipulation
     // With no exaggeration, there are probably millions 
     // of for-loop iterations in this body, in the various functions called. 
     // They also do lots of mallocing and freeing
     // Finally generated some calculated_values

     shared_array1[tst] = calculated_value1;
     shared_array2[tst] = calculated_value2;
     shared_array3[tst] = calculated_value3;

 } // end of parallel and for

// final tidy up

Я полагаю, что не должно быть какой-либосинхронизации — единственный раз, когда потоки обращаются к общей переменной, это shared_arrays, и они обращаются к уникальным точкам в этих массивах. , индексируется tst.

Дело в том, что когда я увеличиваю количество потоков (в многоядерном кластере!) скорости, которые мы видим (где мы запускаем этот цикл 5 раз), следующие;

              Elapsed time   System time
 Serial:        188.149          1.031
 2 thrds:       148.542          6.788
 4 thrds:       309.586        424.037       # SAY WHAT?
 8 thrds:       230.290        568.166  
16 thrds:       219.133        799.780 

Вещи, которые могут быть заметны, — это массивный скачок системного времени между 2 и 4 потоками, а также тот факт, что затраченное время удваивается при переходе от 2 к 4, а затем медленно уменьшается.

Я пробовал с огромным диапазоном параметров OMP_SCHEDULE, но безуспешно. Связано ли это с тем, что каждый поток использует malloc/new и много освобождает/удаляет? Это последовательно выполнялось с памятью 8 ГБ, но я предполагаю, что это не проблема. Честно говоря, огромное увеличение системного времени создает впечатление, что потоки могут блокироваться, но я понятия не имею, почему это может произойти.

ОБНОВЛЕНИЕ 1 Я действительно думал, что ложное совместное использование будет проблемой, поэтому переписал код так, чтобы циклы сохраняли свои вычисленные значения в локальных массивах потока, а затем копировали эти массивы в общий массив в конце. К сожалению, это не оказало никакого влияния, хотя я сам почти не верю в это.

Следуя совету @cmeerw, я запустил strace -f, и после всех инициализаций остались только миллионы строк

[pid 58067] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58066] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 58065] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 57684] <... futex resumed> )       = 0
[pid 58067] <... futex resumed> )       = 0
[pid 58066] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58065] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58067] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58066] <... futex resumed> )       = 0
[pid 57684] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 58065] <... futex resumed> )       = 0
[pid 58067] <... futex resumed> )       = 0
[pid 57684] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 58066] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 58065] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58066] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 57684] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58065] <... futex resumed> )       = 0
[pid 58066] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 57684] <... futex resumed> )       = 0
[pid 58067] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 58066] <... futex resumed> )       = 0
[pid 58065] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58067] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 58066] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 57684] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58065] <... futex resumed> )       = 0
[pid 58067] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58066] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 57684] <... futex resumed> )       = 0
[pid 58067] <... futex resumed> )       = 0
[pid 58066] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58065] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 58066] <... futex resumed> )       = 0
[pid 58065] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 58066] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 57684] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58067] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 58066] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 58065] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 57684] <... futex resumed> )       = 0

У кого-нибудь есть идеи, что это значит? Похоже, потоки слишком часто переключают контекст или просто блокируют и разблокируют? Когда я straceту же реализацию с OMP_NUM_THREADSустановлен на 0, я вообще ничего этого не получаю. Для сравнения, файл журнала, созданный при использовании 1 потока, составляет 486 КБ, а файл журнала, созданный при использовании 4 потоков, составляет 266 МБ.

Другими словами, параллельная версия вызывает дополнительные 4170104 строки файла журнала...

ОБНОВЛЕНИЕ 2

Как предложил Том, я пытался привязать потоки к конкретным процессорам, но безрезультатно. Мы работаем в OpenMP 3.1, поэтому я установил переменную среды, используя export OMP_PROC_BIND=true. Лог-файл того же размера и того же таймфрейма.

ОБНОВЛЕНИЕ 3

Сюжет усложняется. До сих пор профилируя только кластер, я установил GNU GCC 4.7 через Macports и впервые скомпилировал (с openMP) на своем Macbook (Apple GCC-4.2.1 вызывает ошибку компилятора, когда OpenMP включен, поэтому я до сих пор не компилировал и не запускал его параллельно локально). На Macbook вы видите в основном ожидаемую тенденцию

                C-code time
 Serial:         ~34 seconds
 2 thrds:        ~21 seconds
 4 thrds:        ~14 seconds
 8 thrds:        ~12 seconds
16 thrds:         ~9 seconds

Мы видим убывающую доходность к концам, хотя это неудивительно, поскольку пара наборов данных, которые мы повторяем на этих тестовых данных, имеет <16 элементов ( Итак, мы генерируем 16 потоков, скажем, для цикла forс 7 итерациями).

Итак, теперь остается вопрос - ПОЧЕМУ так сильно падает производительность кластера.Сегодня вечером попробую другой четырехъядерный линуксбокс. Кластер компилируется с помощью GNU-GCC 4.6.3, но я не могу поверить, что это само по себе может иметь такое значение?

Ни ltrace, ни GDBне установлены на кластере (и я не могу их установить по разным причинам). Если мой linuxbox дает кластерную производительность, я запускаю соответствующий ltraceанализ.

ОБНОВЛЕНИЕ 4

О боже. Я на дуэли загрузил свой Macbook Pro в Ubuntu (12.04) и повторно запустил код. Все работает (что несколько обнадеживает), но я вижу то же самое странное поведение с низкой производительностью, которое я вижу на кластерах, и тот же самый запуск миллионов вызовов futex. Учитывая, что единственная разница между моим локальным компьютером в Ubuntu и OSX заключается в программном обеспечении (и я использую один и тот же компилятор и библиотеки — по-видимому, нет разных реализаций glibcдля OSX и Ubuntu!) теперь интересно, связано ли это с тем, как Linux планирует/распределяет потоки. В любом случае, работа на моей локальной машине делает все в миллион раз проще, так что я пойду вперед и ltrace -fи посмотрю, что смогу найти. Я написал обходной путь для кластеров, который forks()отделяет отдельный процесс и дает идеальную 1/2 во время выполнения, так что вполне возможно запустить параллелизм...

20
задан Alex 13 June 2012 в 18:14
поделиться