Пример использования сервиса PARSERPLUS
Интернет-сервис PARSERPLUS предлагает два режима работы с исходным кодом программы: автоматический и полуавтоматический. В первом случае исходный код анализируется собственным парсером, который вставляет прагмы OpenMP в явном и в закомментированном виде. Явные прагмы следует воспринимать как рекомендации к исполнению, а закомментированные прагмы скорее служат дополнительной информацией. Во многих случаях автоматически модифицированный код готов к трансляции и часто работает быстрее исходного. Единственное, о чем следует позаботиться пользователю - выполнить трансляцию с ключом использования OpenMP.
В качестве примера автоматической модификации кода приведем исходный код, реализующий простое умножение двух матриц и суммирование элементов матрицы-результата.
#include "stdafx.h"
#include <stdlib.h>
#include <time.h>
#include <omp.h>
int _tmain(int argc, _TCHAR* argv[])
{
clock_t start, finish;
double duration;
double A[200][200];
double B[200][200];
double C[200][200];
int N=200;
start = clock();
double Gs=0;
double ss=0;
for(int cycle=0;cycle<1000;cycle++)
{
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
A[i][j]=2.0*
B[i][j]=2.0*
}
}
for(int ic=0;ic<N;ic++)
{
for(int jc=0;jc<N;jc++)
{
double sum=0;
for(int isum=0;isum<N;isum++)
{
sum+=
}
C[ic][jc]=
}
}
ss=0;
for(int ic=0;ic<N;ic++)
{
for(int jc=0;jc<N;jc++)
{
ss+=C[ic][
}
}
Gs+=ss;
}
finish=clock();
duration
= (double)(finish - start) / CLOCKS_PER_SEC;
printf( "\nProgram takes %10.4f seconds.\n", duration );
printf(" sum=%10.4f\n",Gs);
return 0;
}
На двухъядерном
компьютере с процессором
Intel Core 2 Duo T6600 эта программа,
скомпилированная в
среде Microsoft VS 2010 с опциями
по умолчанию и дополнительным
ключом /openmp выполняется приблизительно
за 15 секунд. После автоматической модификации
кода функции, реализующей умножение двух
матриц, получаем следующий код, который
выполняется на той
же конфигурации за 8 секунд. Модификация
заключается в простом добавлении прагмы #pragma
omp parallel for перед циклом умножения
матриц. Модифицированный участок кода
умножения матриц выглядит так:
#pragma omp parallel for
for(int ic=0;ic<N;ic++)
{
for(int jc=0;jc<N;jc++)
{
double sum=0;
for(int isum=0;isum<N;isum++)
{
sum+=A[ic][
}
C[ic][jc]=sum;
}
}
Имеется небольшая тонкость. Если переменная sum, в которую суммируются значения матрицы результата, определена внутри цикла, как сделано в исходном коде то распараллеленная программа работает корректно. Если же переменная sum определена вне цикла, то для корректной работы потребуется дополнительная прагма reduction(+:sum), которая исключает некорректное обращение к внешней переменной из разных потоков. Некорректность заключается в том, что вследствие гонки данных может нарушаться последовательность операций одного потока: чтение из оперативной памяти переменной sum и запись в регистр; сложение в регистре; запись из регистра в оперативную память. Из второго параллельного потока может записаться другое значение в регистр еще до того как результат работы первого потока запишется в память переменной.Прагма reduction(+:sum)создает локальные копии переменной sum и по окончании цикла их все суммирует.
double sum=0;
#pragma omp parallel for reduction(+:sum)
for(int ic=0;ic<N;ic++)
{
for(int jc=0;jc<N;jc++)
{
sum=0;
for(int isum=0;isum<N;isum++)
{
sum+=A[ic][
}
C[ic][jc]=sum;
}
}
Еще одна простая, но
важная рекомендация заключается в предпочтительном
распараллеливании внешних циклов а не
внутренних. Дело в том, что операция распараллеливания
связана с накладными
расходами и занимает достаточно много
ресурсов вычислительной
системы, и если параллелить внутренний
цикл, время выполнения может превысить
время выполнения исходного последовательного
кода. Например, следующий код, где распараллелен
самый внутренний цикл, выполняется 75
секунд, что в 4.5 раза больше времени выполнения
исходного последовательного кода.
for(int ic=0;ic<N;ic++)
{
for(int jc=0;jc<N;jc++)
{
double sum=0;
#pragma omp parallel for reduction(+:sum)
for(int isum=0;isum<N;isum++)
{
sum+=A[ic][
}
C[ic][jc]=sum;
}
}
Примечания.