Openmp, вложенный в цикл

Привет, у меня есть следующий код. Обеспечивает ли внутренний цикл (цикл for в функции Foo) параллельную производительность? Или мне нужно добавить что-нибудь вроде «коллапса» во внешний цикл, чтобы получить параллельную производительность для внутреннего цикла?

void X(int i)
{
}
void Foo()
{
   ... Do something
   #pragma omp parallel for
   for( int i = 0; i < 10000; ++i )
   {
       X(i);
   }
}
void main()
{
   #pragma omp parallel for
   for( int k = 0; k < 1000; ++k )
   {
     Foo();
   }
}
0
0
1 602
1

Ответы 1

Короче нет, не должно (и не на моей машине).

Во-первых, OpenMP имеет значение, которое устанавливает количество потоков, которые он будет использовать, по умолчанию оно равно количеству потоков, доступных в вашей системе. Таким образом, даже если бы это сработало, первый вызов уже использовал бы все доступные потоки, а второй вызов бы в результате выполнялся бы последовательно. Это значение можно изменить, вызвав omp_set_num_threads(...), но оно является глобальным, поэтому указанное выше поведение останется, то есть первый вызов будет использовать все потоки, которые, по его мнению, доступны. Самый простой способ исправить это - добавить num_threads(...) в прагму omp и заставить ее игнорировать это значение. Конечно, вы также можете использовать там переменные, поэтому num_threads(x) полностью законен, где x - это некоторое целое число, которое даже не нужно знать во время компиляции.

#pragma omp parallel for num_threads(2) //Let's say we want 2 threads in this for loop

Однако одного этого недостаточно, поскольку вложение по умолчанию отключено. Чтобы включить вложение параллельных областей OMP, нам просто нужно вызвать omp_set_nested(true);. Также было бы разумно добавить omp_set_max_active_levels(2);, но в этом нет необходимости, значение по умолчанию кажется больше (я на самом деле не знаю, что это такое, может просто быть неограниченным).

Итак, простая тестовая программа может выглядеть примерно так:

#include <omp.h>
#include <iostream>
#include <string>

int main(void) {
    omp_set_nested(true); //Enables nesting. 
#pragma omp parallel for num_threads(2) //We will be using 2 threads for the first for loop.
    for (int i = 0; i < 10; i++) {
        int tid1 = omp_get_thread_num();
#pragma omp parallel for num_threads(2) //And 2 threads for each of the threads in the second loop.
        //This will require a total of 4 threads. 
        for (int j = 0; j < 10; j++) {
            int tid2 = omp_get_thread_num();
            std::string msg =  std::to_string(i) + " " + std::to_string(j) + " " + std::to_string(tid1) + " " + std::to_string(tid2) + "\n";
            std::cerr << msg;
        }
    }
    return 0;
}

Это покажет, какая комбинация потоков (tid1, tid2) запускает каждую комбинацию (i, j), вывод имеет форму i j tid1 tid2. Если мы закомментируем omp_set_nested(true);, мы заметим, что tid2 всегда является 0, что означает, что внутренний цикл не выполняется параллельно.

Примечание относительно вывода, и cout, и cerr являются потокобезопасными, но это применимо только к одиночным вызовам оператора <<. Таким образом, использование чего-то вроде std::cout << i << " " << j << ... дает вам много вызовов этому оператору из разных потоков и может дать вам несколько чередующихся частей из разных потоков (вы можете попробовать сами, это произойдет не слишком часто, но может). Вот почему мы создаем строку, а затем выполняем единственный вызов <<.

И, как всегда при попытке запустить что-то параллельно, количество потоков, которые вы хотите использовать на каждом уровне, будет зависеть как от типа операций, которые они будут выполнять, так и от машины, на которой вы их запускаете. Вероятно, лучше всего попробовать и немного поиграть, если вы хотите добиться оптимальной производительности. Просто убедитесь, что общее количество потоков (продукт потоков во вложенных циклах) не превышает количество потоков, доступных в вашей системе, что обычно замедляет работу.

Другие вопросы по теме