Распараллеливание программы с помощью OpenMP

From AstroNuWiki
Jump to: navigation, search

OpenMP - библиотека для языков программирования Fortran, C, C++, позволяющая запускать параллельно участки кода на многопроцессорных системах или процессорах с HyperThreading. Встроена в компилятор gcc начиная с версии 4.2. Разрабатывается при активном участии Intel, за что им большое спасибо.

Несколько правил по активации данного режима:

 1) при сборке и связывании программы необходимо указать опцию -fopenmp. 
 2) в коде подключить заголовочный файл 
    #include <omp.h>
 3) если кто-то из пользователей программы будет собирать её с gcc версиями ниже чем 4.2, 
    то в make-файле нужно предусмотреть условие, чтобы опция "-fopenmp" не подключалась, 
    иначе компилятор выдаст ошибку. В программе следует применять обвязку
    #ifdef _OPENMP
     ...
    #endif

Распараллеливание можно ввести на участок программы, то есть определённый блок будет выполняться несколько раз (задаётся пользователем) или для циклов for. На первом случае мы останавливаться не будем, об этом можно прочитать в презентации OpenMP_talk, затронем лишь распараллеливание циклов.

 Простой пример выполнения цикла for с распараллеливанием:
 #include <omp.h>             // подключаем описание библиотеки OpenMP
 #define MAX 100
 int main() 
 {
  int res[MAX], i;
  omp_set_num_threads(2);     // задаём количество параллельных процессов
                              // по умолчанию равно количеству процессоров в системе
  #pragma omp parallel for    // директива, задающая распараллеливание данного цикла
  for (i = 0; i < MAX; i++) {
    res[i] = i*i;
  }
 return 0;
 }

В данном примере цикл for будет выполнятся в два потока, при этом нельзя заранее определить в какой последовательности значений переменной i будет происходит выполнение тела цикла. Таким образом, необходимо избавится от зависимости от предыдущего шага.

Например, если у вас есть такой цикл:

 int i, j, A[MAX];
 j = 5;
 for (i = 0; i < MAX; i++) {
   j += 2;
   A[i] = big(j);
 }

То его нужно преобразовать к виду:

 int i, A[MAX];
 #pragma omp parallel for
 for (i = 0; i < MAX; i++) {
   int j = 5 + 2*i;
   A[i] = big(j);
 }

Очень часто встречается такой случай:

 double ave = 0.0, A[MAX]; int i;
 for (i = 0; i < MAX; i++) {
   ave += A[i];
 }
 ave = ave/MAX;

Переменная ave накапливает значения и зависит от итерации. Если вы запустите программу в параллельном режиме, то можете получать различные ответы. Как я понимаю, дело в том, что чтобы прибавить к переменной ave значение A[i], из памяти в регистр процессора копируется значение ave, суммируется, а затем новое значение записывается в участок памяти этой переменной. В промежутке между считыванием и записью другой процесс может записать новое суммированное значение, которое потом будет потеряно и не учтено.

Учёт такого случая называется reduction и объявляется следующим образом для переменных:

 reduction(op:list)

После чего для каждой переменной list создаётся локальная копия и инициализируется согласно операции op - +, *, - (странно, а / нет) 0, 1, 0 соответственно. После вычисления эта локальная копия комбинируется с глобальной переменной.

Таким образом, корректная запись предыдущего примера выглядит следующим образом:

 double ave=0.0, A[MAX]; int i;
 #pragma omp parallel for reduction (+:ave)
 for (i=0;i< MAX; i++) {
   ave += A[i];
 }
 ave = ave/MAX;

Если вы хотите использовать генератор случайных чисел внутри цикла, который идёт на распараллеливание, то нужно быть очень внимательным, так как если для этого используется задаваемая последовательность псевдослучайных чисел, то они могут перекрываться между собой. Подробно об этом написано в OpenMP_talk.

Итак, вы готовы к программированию распараллеливания циклов. Основные правила - избавиться от зависимостей от итерации, желательно использовать внутри циклов только локальные переменные цикла, внимательно следить за присваиваниями глобальным переменным (выделять эти случаи как reduction). Обязательно сравните результаты до и после.

Внимание! У меня возникли сложности при создании и удалении динамических объектов внутри цикла, так что рекомендую выносить данные операции за его пределы.

Более полная информация содержится зесь: OpenMP_talk, сайт проекта, примеры по использованию.

Оптимальной работы вашему компьютеру!