Во-первых, я знаю, что этот [тип] вопроса часто задают, поэтому позвольте мне предисловие к этому, сказав, что я читал как можно больше, и я до сих пор не знаю, что это за сделка.
Я распараллеливал массивный внешний цикл. Количество итераций цикла варьируется, как правило, между 20-150, но тело цикла выполняет огромную работу, вызывая множество локальных алгоритмов интенсивной линейной алгебры (как и в, код является частью источника, а не внешней зависимости), Внутри тела цикла есть 1000+ вызовов этих подпрограмм, но они полностью независимы друг от друга, поэтому я решил, что это будет главный кандидат на parallelism. Код цикла - С++, но он вызывает много подпрограмм, написанных на 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 и free/delete много? Это постоянно работает с памятью 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-loop
с 7 итерациями).
Итак, теперь остается вопрос - ПОЧЕМУ производительность кластера ухудшается настолько плохо. Сегодня вечером я собираюсь попробовать другой четырехъядерный linuxbox. Кластер компилируется с 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 во время выполнения, поэтому определенно можно получить parallelism...