В последнем примере поведение выполнения зависит от нескольких параметров среды.
Во-первых, OpenMP действительно поддерживает такие шаблоны, но по умолчанию отключает параллельное выполнение во вложенной параллельной области. Чтобы включить его, вы должны установить OMP_NESTED=true
или вызвать omp_set_nested(1)
в своем коде. Затем включается поддержка вложенного параллельного выполнения.
void performAnotherTask() {
// DO something here
}
void performTask() {
// Do other stuff here
#pragma omp parallel for
for (size_t i = 0; i < 100; ++i) {
performAnotherTask();
}
}
int main() {
omp_set_nested(1);
#pragma omp parallel for
for (size_t i = 0; i < 100; ++i) {
performTask();
}
return 0;
}
Во-вторых, когда OpenMP достигает внешней области parallel
, он может захватить все доступные ядра и предположить, что он может выполнить поток на них, поэтому Возможно, вы захотите уменьшить количество потоков для внешнего уровня, чтобы некоторые ядра были доступны для вложенной области. Скажем, если у вас 32 ядра, вы можете сделать это:
void performAnotherTask() {
// DO something here
}
void performTask() {
// Do other stuff here
#pragma omp parallel for num_threads(8)
for (size_t i = 0; i < 100; ++i) {
performAnotherTask();
}
}
int main() {
omp_set_nested(1);
#pragma omp parallel for num_threads(4)
for (size_t i = 0; i < 100; ++i) {
performTask();
}
return 0;
}
Внешняя параллельная область будет выполняться с использованием 4 потоков, каждый из которых будет выполнять внутреннюю область с 8 потоками. Обратите внимание, что каждый из 4 внешних потоков будет одним из главных потоков четырех одновременно выполняемых вложенных параллельных областей. Если вы хотите быть более гибким, вы можете ввести количество потоков для каждого уровня, используя переменную окружения OMP_NUM_THREADS
. Если вы установите его на OMP_NUM_THREADS=4,8
, вы получите то же поведение, что и выше, в первом фрагменте кода, который я разместил.
Проблема с шаблоном кодирования заключается в том, что вам нужно быть осторожным при балансировке каждого уровня, чтобы не перегрузить систему или получить дисбаланс нагрузки между вложенными параллельными областями. Альтернативное решение - вместо этого использовать задачи OpenMP:
void performAnotherTask() {
// DO something here
}
void performTask() {
// Do other stuff here
#pragma omp taskloop
for (size_t i = 0; i < 100; ++i) {
performAnotherTask();
}
}
int main() {
omp_set_nested(1);
#pragma omp parallel
#pragma omp single
#pragma omp taskloop
for (size_t i = 0; i < 100; ++i) {
performTask();
}
return 0;
}
Здесь каждая из конструкций taskloop
будет генерировать задачу OpenMP, запланированную для выполнения в потоках, созданных одной parallel
областью. в коде. Предостережение заключается в том, что задачи по своей природе динамичны c, поэтому вы можете потерять свойства локальности, поскольку не знаете, где именно задачи будут выполняться в системе.