Во-первых, я знаю, что этот [тип] вопроса часто задают, поэтому позвольте мне предварить его, сказав, что я прочитал столько, сколько мог, и я все еще не знаю. знать, в чем дело.
Я распараллелил массивныйвнешний цикл 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 во время выполнения, так что вполне возможно запустить параллелизм...