#gcc #openmp
#gcc #openmp
Вопрос:
У меня есть следующая программа. nv составляет около 100, dgemm — 20×100 или около того, так что предстоит проделать еще много работы:
#pragma omp parallel for schedule(dynamic,1)
for (int c = 0; c < int(nv); c) {
omp::thread thread;
matrix amp;t3_c = vv_.at(omp::num_threads() thread);
if (terms.first) {
blas::gemm(1, t2_, vvvo_, 1, t3_c);
blas::gemm(1, vvvo_, t2_, 1, t3_c);
}
matrix amp;t3_b = vv_[thread];
if (terms.second) {
matrix amp;t2_ci = vo_[thread];
blas::gemm(-1, t2_ci, Vjk_, 1, t3_c);
blas::gemm(-1, t2_ci, Vkj_, 0, t3_b);
}
}
однако с GCC 4.4, GOMP v1, на gomp_barrier_wait_end
приходится почти 50% времени выполнения. Изменение GOMP_SPINCOUNT
снижает нагрузку, но тогда используется только 60% ядер. То же самое для OMP_WAIT_POLICY=passive
. Система Linux, 8 ядер.
Как я могу добиться полного использования без перезаписи / ожидания переполнения
Комментарии:
1. Просто ради интереса попробуйте изменить расписание на использование «schedule (static)» и посмотрите, что произойдет.
2. @ejd тоже пробовал это, тот же эффект.
3. @Anycorn: вы решили проблему? У меня такая же проблема…
Ответ №1:
Барьер — это симптом, а не проблема. Причина, по которой в конце цикла возникает много ожиданий, заключается в том, что некоторые потоки выполняются намного раньше других, и все они довольно долго ждут в конце цикла for, пока все не будут выполнены.
Это классическая проблема с дисбалансом нагрузки, которая здесь странная, поскольку это всего лишь набор матричных умножений. Они разного размера? Как они размещены в памяти, с точки зрения материала NUMA — все ли они в настоящее время находятся в кэше одного ядра или есть другие проблемы с общим доступом? Или, проще говоря — существует ли только 9 матриц, так что оставшиеся 8 обречены застрять в ожидании того, кто получил последнюю?
Когда подобные вещи происходят в более крупном параллельном блоке кода, иногда можно перейти к следующему блоку кода, пока некоторые итерации цикла еще не выполнены; там вы можете добавить nowait
директиву к for, которая переопределит поведение по умолчанию и избавит от подразумеваемого барьера. Здесь, однако, поскольку параллельный блок в точности соответствует размеру цикла for, это действительно не может помочь.
Комментарии:
1. имеется 100 матриц, дисбаланс нагрузки крайне маловероятен. ложный обмен также маловероятен. nowait значительно ухудшает ситуацию с точки зрения скорости.
2. Маловероятно или нет, если бы не было проблем с дисбалансом нагрузки, все ваши задачи не ожидали бы завершения omp для. nowait также должен иметь ровно нулевую разницу здесь, если код такой, как указано выше; он должен просто сдвинуть подразумеваемый барьер с конца for на конец параллельного блока…
3. и, как предположил комментарий @ejd, я подозреваю, что причина, по которой расписание в настоящее время (динамическое, 1), заключается в том, что с расписанием по умолчанию все было хуже и даже динамично с большим размером фрагмента, да? Опять же, это признак проблемы с дисбалансом нагрузки. Один из способов увидеть, что происходит, если ваши инструменты профилирования не делают этого за вас, — рассчитать время каждой итерации цикла и распечатать время с соответствующим индексом потока и номером итерации и посмотреть, сможете ли вы выяснить, откуда возникает дисбаланс.
Ответ №2:
Может ли быть так, что ваша реализация BLAS также вызывает OpenMP внутри? Если только вы не видите только один вызов gomp_barrier_wait_end
.
Комментарии:
1. Это определенно идея, которую стоит проверить. Даже если он использует pthreads, там может быть некоторое взаимодействие.
2. В любом случае, вы не должны так сильно беспокоиться о барьере (если только ваша цель не состоит в том, чтобы уменьшить накладные расходы на барьер), но для вашего времени работы в настенном режиме: какое ускорение вы получаете?