- стандарт программного интерфейса приложений для параллельных систем с общей памятью. Поддерживает языки C, C++, Фортран.
(рис 2.1) Модель параллельной программы в OpenMPМодель параллельной программы в можно сформулировать следующим образом:
составляют следующие компоненты:
UNIX ).Использование директив компилятора и подпрограмм библиотеки времени выполнения подчиняется правилам, которые различаются для разных языков программирования. Совокупность таких правил называется привязкой к языку.
В программах на языке Fortran OMP. Формат
{!|C|*}$OMP директива [оператор_1[, оператор_2, ...]]
Директива начинается в первой (фиксированный формат записи текста языка Fortran 77 ) или произвольной (свободный формат) позиции строки. Допускается продолжение директивы в следующей строке,
в этом случае действует стандартное в данной версии языка правило для обозначения строки продолжения (непробельный символ в шестой позиции для фиксированного формата записи и амперсанд для свободного формата).
Пример программы на языке Fortran с использованием OpenMP
program omp_example integer i, k, N real*4 sum, h, x print *, "Please, type in N:" read *, N h = 1.0 / N sum = 0.0 C$OMP PARALLEL DO SCHEDULE(STATIC) REDUCTION(+:sum) do i = 1, N x = i * h sum = sum + 1.e0 * h / (1.e0 + x**2) enddo print *, 4.0 * sum end
В программах на языке C прагмы, имена функций и переменных окружения OMP начинаются с omp, omp или OMP. Формат директивы:
#
В -программе используется заголовочный файл omp.h.
Пример программы на языке С с использованием OpenMP
#include "omp.h" #include <stdio.h> double f(double x) {
return 4.0 / (1 + x * x);
}
main() {
const long N = 100000;
long i;
double h, sum, x; sum = 0;
h = 1.0 / N;
#pragma omp parallel shared(h) {
#pragma omp for private(x) reduction(+:sum) for (i = 0; i < N; i++)
{ x = h * (i + 0.5);
sum = sum + f(x);
}
}
printf("PI = %f\n", sum / N);
}
Далее приводится перечень директив . Описания директив ориентированы на спецификацию версии 2.5.
parallel . . . end parallel
Задает границы параллельной секции программы. С данной директивой могут использоваться следующие операторы (их описание дается далее):
do цикл do end do #pragma omp for цикл for
Задает границы цикла, исполняемого в параллельном режиме в языках Fortran и C соответственно. С данной директивой могут использоваться следующие операторы:
sections ... end sections
Обрамляет параллельную секцию программы. Вложенные секции программы, задаваемые директивами section, распределяются между нитями. С данной директивой могут использоваться следующие операторы:
section
Определяет часть sections, которая выполняется одной нитью.
single ... end single
Обрамляет блок программы, который должен выполняться одной нитью. С данной директивой могут использоваться следующие операторы:
workshare ... end workshare
Делит блок на части, выполнение которых распределяется между нитями таким образом, что каждая часть выполняется один раз. Блок может содержать только следующие конструкции:
parallel do цикл do end parallel do
Объединяет директивы parallel и do.
parallel sections ... end parallel sections
Объединяет директивы parallel и sections.
parallel workshare ... end parallel workshare
Объединяет директивы parallel и workshare.
master ... end master
Обрамляет блок программы, который должен выполняться только главной нитью.
critical[(блокировка)] ... end critical[(блокировка)]
Обрамляет блок программы, доступ к которому в любой момент времени может получить только одна нить (
Директива барьерной синхронизации нитей. Каждая нить, выполнение которой достигло данной точки, приостанавливает свое выполнение до тех пор, пока все нити не достигнут данной точки.
atomic
Объявляет операцию атомарной (при выполнении
x = x {+|-|*|/|.AND.|.OR.|.EQV.|.NEQV.} скалярное_выражение_не_содержащее_хx = скалярное_выражение_не_содержащее_х {+|-|*|/|.AND.|.OR.|.EQV.|.NEQV.} xx = {MAX|MIN|IAND|IOR|IEOR} (x, скалярное_выражение_не_содержащее_x)x = {MAX|MIN|IAND|IOR|IEOR} (скалярное_выражение_не_содержащее_x, x)Данная директива задает точку синхронизации, в которой значения переменных, указанных в списке и видимых из данной нити, записываются в память. Этим обеспечивается согласование содержимого памяти, доступного разным нитям.
ordered ... end ordered
Обеспечивает сохранение того порядка выполнения итераций цикла, который соответствует последовательному выполнению программы.
threadprivate(список common-блоков)
Определяет common -блоки, перечисленные в списке, локальными.
Операторы используются совместно с директивами.
private(
Объявляет переменные из списка локальными.
firstprivate(
Объявляет переменные из списка локальными и инициализирует их значениями из блока программы, предшествующего данной директиве.
lastprivate(
Объявляет переменные из списка локальными и назначает им значения из того блока программы, который был выполнен последним.
copyprivate(
После завершения выполнения блока, заданного директивой single, значения локальных переменных из списка распределяются между остальными нитями.
Отменяет барьерную синхронизацию при завершении выполнения параллельной секции.
shared(
Объявляет переменные из списка общими для всех нитей.
default(private|shared|none)
Данный оператор позволяет изменить правила определения области видимости переменных, действующие по умолчанию. Вариант private используется только в языке Fortran.
Оператор приведения (редукции) значений локальных переменных из списка с помощью указанной операции или встроенной функции языка. Операция редукции применяется к нескольким значениям и возвращает одно значение.
if(скалярное логическое выражение)
Условный оператор.
num_threads( скалярное целое выражение)
Задает количество нитей. Альтернативный способ задания количества нитей обеспечивает переменная окружения OMP_NUM_THREADS.
schedule(характер_распределения_итераций[, количество_итераций_цикла])
Данный оператор задает способ распределения итераций цикла между нитями:
static - количество итераций цикла, передаваемых для выполнения каждой нити фиксировано и распределяется между нитями по принципу кругового планирования.
Если количество итераций не указано, оно полагается равным 1;dynamic - количество итераций цикла, передаваемых для выполнения каждой нити фиксировано. Очередная "порция" итераций передается освободившейся нити;guided - количество итераций цикла, передаваемых для выполнения каждой нити постепенно уменьшается. Очередная "порция" итераций передается освободившейся нити;runtime - тип распределения работы определяется во время выполнения программы, например, с помощью переменной окружения OMP_SCHEDULE.copyin(список имен common-блоков)
При выполнении этого оператора данные из главной нити копируются в локальные экземпляры common -блока в начале каждой параллельной секции. Имена задаются между символами "/ ".
Здесь и далее вначале приводится интерфейс подпрограмм для языка C, затем для языка Fortran.
void omp_set_num_threads(int threads); subroutine omp_set_num_threads(threads) integer threads
Задает количество потоков ( threads ) при выполнении параллельных секций программы.
int omp_get_num_threads(void); integer function omp_get_num_threads()
Возвращает количество потоков, используемых для выполнения параллельной секции.
int omp_get_max_threads(void); integer function omp_get_max_threads()
Возвращает максимальное количество потоков, которые можно использовать для выполнения параллельных секций программы.
int omp_get_thread_num(void); integer function omp_get_thread_num()
Возвращает идентификатор нити, из которой вызывается данная функция.
int omp_get_num_procs(void); integer function omp_get_num_procs()
Возвращает количество процессоров, доступных в данный момент программе.
int omp_in_parallel(void); logical function omp_in_parallel()
Возвращает значение true при вызове из активной параллельной секции программы.
void omp_set_dynamic(int threads); subroutine omp_set_dynamic(threads) logical threads
Включает или отключает возможность динамического назначения количества потоков при выполнении параллельной секции. По умолчанию эта возможность отключена.
int omp_get_dynamic(void); logical function omp_get_dynamic()
Возвращает значение true, если динамическое назначение количества потоков разрешено.
void omp_set_nested(int nested); subroutine omp_set_nested(nested) integer nested
Разрешает или запрещает вложенный параллелизм. По умолчанию вложенный параллелизм запрещен.
int omp_get_nested(void); logical function omp_get_nested()
Определяет, разрешен ли вложенный параллелизм.
Блокировки используются для предотвращения эффектов, приводящих к непредсказуемому (недетерминированному) поведению программы. Среди таких эффектов, например, гонки за данными, когда более чем один поток имеет доступ к одной и той же переменной.
void omp_init_lock(omp_lock_t *lock); subroutine omp_init_lock(lock) integer(kind = omp_lock_kind) :: lock
Инициализирует блокировку, связанную с идентификатором lock, для использования в последующих вызовах подпрограмм.
void omp_destroy_lock(omp_lock_t *lock); subroutine omp_destroy_lock(lock) integer(kind = omp_lock_kind) :: lock
Переводит блокировку, связанную с идентификатором lock, в состояние неопределенности.
void omp_set_lock(omp_lock_t *lock); subroutine omp_set_lock(lock) integer(kind = omp_lock_kind) lock
Переводит потоки из состояния выполнения в состояние ожидания до тех пор, пока блокировка, связанная с идентификатором lock, не окажется доступной. Поток становится владельцем доступной блокировки.
void omp_unset_lock(omp_lock_t *lock); subroutine omp_unset_lock(lock) integer(kind = omp_lock_kind) :: lock
После выполнения данного вызова поток перестает быть владельцем блокировки, связанной с идентификатором lock. Если поток не был владельцем блокировки, результат вызова не определен.
int omp_test_lock(omp_lock_t *lock); logical function omp_test_lock(lock) integer(kind = omp_lock_kind) :: lock
Возвращает значение "истина", если блокировка связана с идентификатором lock.
void omp_init_nest_lock(omp_nest_lock_t *lock); subroutine omp_init_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock
Инициализирует вложенную блокировку, связанную с идентификатором lock.
void omp_destroy_nest_lock(omp_nest_lock_t *lock); subroutine omp_destroy_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock
Переводит вложенную блокировку, связанную с идентификатором lock, в состояние неопределенности.
void omp_set_nest_lock(omp_nest_lock_t *lock); subroutine omp_set_nest_lock(lock) integer(kind= omp_nest_lock_kind) :: lock
Переводит выполняющиеся потоки в состояние ожидания до тех пор, пока вложенная блокировка, связанная с идентификатором lock, не станет доступной. Поток становится владельцем блокировки.
void omp_unset_nest_lock(omp_nest_lock_t *lock); subroutine omp_unset_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock
Освобождает выполняющийся поток от владения вложенной блокировкой, связанной с идентификатором lock. Если поток не был владельцем блокировки, результат не определен.
int omp_test_nest_lock(omp_nest_lock_t *lock); integer function omp_test_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock
Функция, позволяющая определить, связана ли вложенная блокировка с идентификатором lock . Если связана, возвращается значение счетчика, в противном случае возвращается значение 0.
Для профилирования программы можно использовать таймеры.
double omp_get_wtime(void); double precision function omp_get_wtime()
Возвращает время в секундах, прошедшее с произвольного момента в прошлом. Точка отсчета остается неизменной в течение всего времени выполнения программы.
double omp_get_wtick(void); double precision function omp_get_wtick()
Возвращает время в секундах, прошедшее между последовательными "тиками". Это время является мерой точности таймера.
Переменные окружения задаются следующим образом:
export ПЕРЕМЕННАЯ=значение (в среде UNIX )set ПЕРЕМЕННАЯ=значение (в среде Microsoft Windows )OMP_NUM_THREADS
Задает количество нитей при выполнении параллельных секций программы.
OMP_SCHEDULE
Задает способ распределения итераций циклов между нитями. Возможные значения:
static ;dynamic ;guidedКоличество итераций (необязательный параметр) указывается после одного из этих ключевых слов, отделяясь от него запятой, например:
export OMP_SCHEDULE="static, 10" OMP_DYNAMIC
Если этой переменной присвоено значение false, динамическое распределение итераций циклов между нитями запрещено, если true - разрешено.
OMP_NESTED
Если этой переменной присвоено значение false, вложенный параллелизм запрещен, если true - разрешен.
Параллельная -программа состоит из последовательных и параллельных секций. Границы параллельных секций обозначаются директивами . Процесс разработки -программы включает следующие этапы:
Профилирование может производиться как с помощью специальных программных инструментов, так и простыми средствами, например, с помощью вызова специальных подпрограмм-таймеров, размещенных в различных местах программы.
Цикл эффективно распараллеливается, если отсутствуют перекрестные зависимости между его итерациями. Избавиться от таких зависимостей иногда можно, выполнив преобразование цикла.
Необходимо правильно определить область видимости переменных в параллельных секциях программы. Параметр цикла, например, должен быть объявлен локальной переменной.
При вычислении суммы, например, к переменной, которая используется для "накопления" суммы, должна быть применена
Следует обратить внимание на синхронизацию вычислений. По умолчанию в циклах используется барьерная синхронизация. Наличие синхронизаций увеличивает предсказуемость поведения программы, но замедляет ее работу.
Дополнительный выигрыш в производительности дает объединение нескольких параллельных секций в одну. В этом случае уменьшаются накладные расходы на запуск нитей и их завершение.
Трансляция -программы выполняется со специальным ключом. В операционной системе Linux транслятор $$Intel\text{\textregistered} Compiler$$ использует ключ - , например:
#ifort -o my_prog prog_source.f9 0 -
В операционной системе $$Microsoft\text{\textregistered} Windows$$ командная строка выглядит следующим образом:
#ifort prog_source.f9 0 /Qopenmp
Приближенное вычисление интеграла:
$$I=\int\limits_{x_o}^{x_1}F(x)dx$$основано на его замене конечной суммой:
$$I_n=\sum\limits_{k=0}^n w_kF(x_k)$$где $$w_k$$ - числовые коэффициенты, а $$x_k$$ - точки отрезка $$[x_0, x_1]$$. Приближенное равенство:
$$I\approx I_n$$называется квадратурной формулой, точки $$x_k$$ - узлами квадратурной формулы, а числа $$w_k$$ - коэффициентами квадратурной формулы. Разные методы приближенного интегрирования отличаются выбором узлов и коэффициентов. От этого выбора зависит погрешность квадратурной формулы:
$$R_n=|I-I_n|$$Интегрирование методом трапеций - основано на использовании кусочно-линейного приближения для интегрируемой функции. Пусть $$F (x)$$ - гладкая функция на интервале $$[a,b]$$, и этот интервал делится на $$n$$ равных частей, каждая длиной $$h=\frac{b-a}{n}$$.
Приближение метода трапеций:
$$I(h)=\frac{h[f_0+2f_1+2f_2+\ldots+2f_{n-1}+f_n]}{2}$$где $$f_i = F (a + jh)$$ - значение интегрируемой функции в точке $$a + jh$$.
Идея трехточечного метода Симпсона заключается в следующем. Пусть $$x_m$$ - это средняя точка интервала $$[x_0, x_1]$$ и пусть $$Q (x)$$ - единственный полином второй степени, который интерполирует (приближает) подынтегральную функцию $$F(x)$$ по точкам $$x_0,$$ $$x_m$$ и $$x_1$$. Искомый интеграл аппроксимируется интегралом от функции $$Q (x)$$:
$$I_i\approx \int\limits_{x_i}^{x_{i+1}}Q(x)dx $$Эта оценка точна, если $$F (x)$$ является полиномом степени 3.
Обычно используются составные квадратурные формулы, когда промежуток интегрирования разбивается на $$N$$ подынтервалов и простая формула Симпсона применяется на каждом из этих подынтервалов:
$$I_i\approx \int\limits_{x_i}^{x_{i+1}}Q(x)dx $$$$I=\sum\limits_{i=1}^N I_i$$Недостатком рассмотренного метода является то, что он не дает возможности явно задать точность вычисления интеграла. Точность связана с количеством точек разбиения. От этого недостатка свободны методы интегрирования с адаптивным выбором шага разбиения. Если трехточечный
В заданиях лабораторной работы 1.1 предлагается выполнить распараллеливание последовательных программ, предназначенных для вычисления определенных интегралов. В задании 4 распараллеливание производится с помощью MPICH 1.2.7. Цель работы - получить навык анализа простых программ и выявления в них потенциального параллелизма,
применить для распараллеливания и MPI, сравнить трудоемкость обоих подходов и эффективность полученного результата. Звездочкой отмечено задание повышенной сложности.
Необходимый для выполнения данной лабораторной работы справочный материал можно найти на стр. 13 - 24 методического пособия "Средства программирования для многопроцессорных вычислительных систем".
Получить у преподавателя файл с исходным текстом программы (примеры 1, 2) и ознакомиться с реализацией квадратурной формулы.
Откомпилировать программу, выполнить расчет. Определить процессорное время, потраченное на выполнение расчета.
Проанализировать последовательный код и выявить участки потенциального параллелизма. Выполнить распараллеливание с помощью . Определить процессорное время, потраченное на выполнение расчета для разного числа потоков (меньшего, равного и большего, чем число процессоров). Сравнить с результатом, полученным в задании 2. Объяснить полученный результат.
Распараллелить программу с помощью MPI. Определить процессорное время, потраченное на выполнение расчета. Сравнить с результатами, полученными в заданиях 2
и 3.
На основании результатов, полученных при выполнении заданий данной лабораторной работы, написать отчет, в котором содержатся выводы об эффективности различных способов распараллеливания исходного последовательного кода и трудоемкости реализации этих способов на практике.
В программе на языке Fortran 90 реализован
program integral_trapez
integer, parameter :: div_no = 100
real, parameter :: x0 = 0., xl = 1. !3.14159
real, external :: F
real :: result
result = trapezium(F, x0, x1, div_no)
print *, result
end
real function trapezium(F, x0, x1, div_no)
real, external :: F
real, intent(in) :: x0, x1
integer, intent(in) :: div_no
real :: x, dx, sum integer :: j
dx = (x1 - x0) / div_no
sum = F(x0) + F(x1)
x = x0
do j = 1, div_no - 1
x = x + dx
sum = sum + 2.0 * F(x)
end do
trapezium = dx * sum / 2.0
end
real function F(x) real, intent(in) :: x !F= sin(x)
F = 4./(1.+x**2)
end
В программе на языке Fortran 90 реализован
program integral_simps
integer, parameter :: div_no = 100
real, parameter :: x0 = 0., x1 = 1. !3.14159
real, external :: F
real :: result
result = simpson(F, x0, x1, div_no) print *, result
end
real function simpson(F, x0, x1, div_no)
real, external :: F
real, intent(in) :: x0, x1
integer, intent(in) :: div_no
real :: x, dx, sum integer :: j
dx = (x1 - x0) / (2.0 * div_no)
sum = F(x0) + F(x1)
x = x0
do j = 1, 2 * div_no - 1
x = x + dx
if (mod(j, 2) /= 0) then
sum = sum + 4.0 * F(x)
else
sum = sum + 2.0 * F(x)
end if
end do
simpson = dx * sum / 3.0
end
real function F(x) real, intent(in) :: x !F= sin(x)
F = 4./(1.+x**2)
End
Параллельная -программа состоит из последовательных и параллельных секций. Границы параллельных секций обозначаются директивами . Процесс разработки -программы включает следующие этапы:
Профилирование может производиться как с помощью специальных программных инструментов, так и простыми средствами, например, с помощью вызова специальных подпрограмм-таймеров, размещенных в различных местах программы.
Цикл эффективно распараллеливается, если отсутствуют перекрестные зависимости между его итерациями. Избавиться от таких зависимостей иногда можно, выполнив преобразование цикла.
Необходимо правильно определить область видимости переменных в параллельных секциях программы. Параметр цикла, например, должен быть объявлен локальной переменной.
При вычислении суммы, например, к переменной, которая используется для "накопления" суммы, должна быть применена
Следует обратить внимание на синхронизацию вычислений. По умолчанию в циклах используется барьерная синхронизация. Наличие синхронизаций увеличивает предсказуемость поведения программы, но замедляет ее работу.
Дополнительный выигрыш в производительности дает объединение нескольких параллельных секций в одну. В этом случае уменьшаются накладные расходы на запуск нитей и их завершение.
Трансляция -программы выполняется со специальным ключом. В операционной системе Linux транслятор $$Intel\text{\textregistered} Compiler$$ использует ключ - , например:
#ifort -o my_prog prog_source.f9 0 -
В операционной системе $$Microsoft\text{\textregistered} Windows$$ командная строка выглядит следующим образом:
#ifort prog_source.f9 0 /Qopenmp
Классическим численным методом решения систем линейных алгебраических уравнений:
$$Ax=b$$где $$A=\|a_{ij}\|_{i,j=1,\ldots n}$$ - квадратная матрица коэффициентов, $$x$$ - вектор неизвестных, а $$b$$ - вектор правой части, является
Приведем описание алгоритма для
Очевидным условием применимости
В заданиях лабораторной работы 2.1 предлагается выполнить распараллеливание последовательной программы, предназначенной для решения систем линейных алгебраических уравнений. В задании 4 распараллеливание производится с помощью MPICH 1.2.7. Цель работы - получить навык анализа программ,
выявления в них участков потенциального параллелизма с наибольшей трудоемкостью, применить для распараллеливания и MPI, сравнить трудоемкость обоих подходов и эффективность полученного результата. Звездочкой отмечено задание повышенной сложности.
Необходимый для выполнения данной лабораторной работы справочный материал можно найти на стр. 13 - 24 методического пособия "Средства программирования для многопроцессорных вычислительных систем".
Получить у преподавателя файл с исходным текстом программы (пример 1) и ознакомиться с реализацией простого
Откомпилировать программу, выполнить расчет. Определить процессорное время, потраченное на выполнение расчета.
Проанализировать последовательный код. Выявить участки потенциального параллелизма с наибольшей трудоемкостью. Для этого следует подсчитать количество операций для прямого хода .
Определить процессорное время, потраченное на выполнение расчета для разного числа потоков (меньшего, равного и большего, чем число процессоров). Сравнить с результатом, полученным в задании 2. Объяснить полученный результат.
Распараллелить программу с помощью MPI. Определить процессорное время, потраченное на выполнение расчета. Сравнить с результатами, полученными в заданиях 2 и 3.
На основании результатов, полученных при выполнении заданий данной лабораторной работы, написать отчет, в котором содержатся выводы об эффективности различных способов распараллеливания исходного последовательного кода и трудоемкости реализации этих способов на практике.
В программе на языке Fortran 90 реализован простой
program linear_algebra_gauss
integer, parameter :: n = 3
real, dimension(1:n, 1:n) :: a, left
real, dimension(1:n) :: x, b
data left/9 * 0./
data a/.471, 4.27, .012, 3.21, -.513, 1.273, -1.307, 1.102, -
4.175 /
data b/ 2.425, -.176, 1.423 /
! Решение: 0.07535443 0.6915624 -0.1297573
! Прямой ход метода Гаусса
do k = 1, n - 1
do i = k + 1, n
left(i, k) = a(i, k) / a(k, k)
b(i) = b(i) - left(i, k) * b(k)
end do
do j = k + 1, n
do i = k + 1, n
a(j, i) = a(j, i) - left(j, k) * a(k, i)
end do
end do
end do
! Обратная подстановка x(n) = b(n) / a(n, n)
do i = n - 1, 1, -1
x(i) = b(i)
do j = i + 1, n
x(i) = x(i) - a(i, j) * x(j)
end do
x(i) = x(i) / a(i, i)
end do
print *, (x(i), i = 1, n)
end
- стандарт программного интерфейса приложений для параллельных систем с общей памятью. Поддерживает языки C, C++, Фортран.
(рис 2.1) Модель параллельной программы в OpenMPМодель параллельной программы в можно сформулировать следующим образом:
составляют следующие компоненты:
UNIX ).Использование директив компилятора и подпрограмм библиотеки времени выполнения подчиняется правилам, которые различаются для разных языков программирования. Совокупность таких правил называется привязкой к языку.
В программах на языке Fortran OMP. Формат
{!|C|*}$OMP директива [оператор_1[, оператор_2, ...]]
Директива начинается в первой (фиксированный формат записи текста языка Fortran 77 ) или произвольной (свободный формат) позиции строки. Допускается продолжение директивы в следующей строке,
в этом случае действует стандартное в данной версии языка правило для обозначения строки продолжения (непробельный символ в шестой позиции для фиксированного формата записи и амперсанд для свободного формата).
Пример программы на языке Fortran с использованием OpenMP
program omp_example integer i, k, N real*4 sum, h, x print *, "Please, type in N:" read *, N h = 1.0 / N sum = 0.0 C$OMP PARALLEL DO SCHEDULE(STATIC) REDUCTION(+:sum) do i = 1, N x = i * h sum = sum + 1.e0 * h / (1.e0 + x**2) enddo print *, 4.0 * sum end
В программах на языке C прагмы, имена функций и переменных окружения OMP начинаются с omp, omp или OMP. Формат директивы:
#
В -программе используется заголовочный файл omp.h.
Пример программы на языке С с использованием OpenMP
#include "omp.h" #include <stdio.h> double f(double x) {
return 4.0 / (1 + x * x);
}
main() {
const long N = 100000;
long i;
double h, sum, x; sum = 0;
h = 1.0 / N;
#pragma omp parallel shared(h) {
#pragma omp for private(x) reduction(+:sum) for (i = 0; i < N; i++)
{ x = h * (i + 0.5);
sum = sum + f(x);
}
}
printf("PI = %f\n", sum / N);
}
Далее приводится перечень директив . Описания директив ориентированы на спецификацию версии 2.5.
parallel . . . end parallel
Задает границы параллельной секции программы. С данной директивой могут использоваться следующие операторы (их описание дается далее):
do цикл do end do #pragma omp for цикл for
Задает границы цикла, исполняемого в параллельном режиме в языках Fortran и C соответственно. С данной директивой могут использоваться следующие операторы:
sections ... end sections
Обрамляет параллельную секцию программы. Вложенные секции программы, задаваемые директивами section, распределяются между нитями. С данной директивой могут использоваться следующие операторы:
section
Определяет часть sections, которая выполняется одной нитью.
single ... end single
Обрамляет блок программы, который должен выполняться одной нитью. С данной директивой могут использоваться следующие операторы:
workshare ... end workshare
Делит блок на части, выполнение которых распределяется между нитями таким образом, что каждая часть выполняется один раз. Блок может содержать только следующие конструкции:
parallel do цикл do end parallel do
Объединяет директивы parallel и do.
parallel sections ... end parallel sections
Объединяет директивы parallel и sections.
parallel workshare ... end parallel workshare
Объединяет директивы parallel и workshare.
master ... end master
Обрамляет блок программы, который должен выполняться только главной нитью.
critical[(блокировка)] ... end critical[(блокировка)]
Обрамляет блок программы, доступ к которому в любой момент времени может получить только одна нить (
Директива барьерной синхронизации нитей. Каждая нить, выполнение которой достигло данной точки, приостанавливает свое выполнение до тех пор, пока все нити не достигнут данной точки.
atomic
Объявляет операцию атомарной (при выполнении
x = x {+|-|*|/|.AND.|.OR.|.EQV.|.NEQV.} скалярное_выражение_не_содержащее_хx = скалярное_выражение_не_содержащее_х {+|-|*|/|.AND.|.OR.|.EQV.|.NEQV.} xx = {MAX|MIN|IAND|IOR|IEOR} (x, скалярное_выражение_не_содержащее_x)x = {MAX|MIN|IAND|IOR|IEOR} (скалярное_выражение_не_содержащее_x, x)Данная директива задает точку синхронизации, в которой значения переменных, указанных в списке и видимых из данной нити, записываются в память. Этим обеспечивается согласование содержимого памяти, доступного разным нитям.
ordered ... end ordered
Обеспечивает сохранение того порядка выполнения итераций цикла, который соответствует последовательному выполнению программы.
threadprivate(список common-блоков)
Определяет common -блоки, перечисленные в списке, локальными.
Операторы используются совместно с директивами.
private(
Объявляет переменные из списка локальными.
firstprivate(
Объявляет переменные из списка локальными и инициализирует их значениями из блока программы, предшествующего данной директиве.
lastprivate(
Объявляет переменные из списка локальными и назначает им значения из того блока программы, который был выполнен последним.
copyprivate(
После завершения выполнения блока, заданного директивой single, значения локальных переменных из списка распределяются между остальными нитями.
Отменяет барьерную синхронизацию при завершении выполнения параллельной секции.
shared(
Объявляет переменные из списка общими для всех нитей.
default(private|shared|none)
Данный оператор позволяет изменить правила определения области видимости переменных, действующие по умолчанию. Вариант private используется только в языке Fortran.
Оператор приведения (редукции) значений локальных переменных из списка с помощью указанной операции или встроенной функции языка. Операция редукции применяется к нескольким значениям и возвращает одно значение.
if(скалярное логическое выражение)
Условный оператор.
num_threads( скалярное целое выражение)
Задает количество нитей. Альтернативный способ задания количества нитей обеспечивает переменная окружения OMP_NUM_THREADS.
schedule(характер_распределения_итераций[, количество_итераций_цикла])
Данный оператор задает способ распределения итераций цикла между нитями:
static - количество итераций цикла, передаваемых для выполнения каждой нити фиксировано и распределяется между нитями по принципу кругового планирования.
Если количество итераций не указано, оно полагается равным 1;dynamic - количество итераций цикла, передаваемых для выполнения каждой нити фиксировано. Очередная "порция" итераций передается освободившейся нити;guided - количество итераций цикла, передаваемых для выполнения каждой нити постепенно уменьшается. Очередная "порция" итераций передается освободившейся нити;runtime - тип распределения работы определяется во время выполнения программы, например, с помощью переменной окружения OMP_SCHEDULE.copyin(список имен common-блоков)
При выполнении этого оператора данные из главной нити копируются в локальные экземпляры common -блока в начале каждой параллельной секции. Имена задаются между символами "/ ".
Здесь и далее вначале приводится интерфейс подпрограмм для языка C, затем для языка Fortran.
void omp_set_num_threads(int threads); subroutine omp_set_num_threads(threads) integer threads
Задает количество потоков ( threads ) при выполнении параллельных секций программы.
int omp_get_num_threads(void); integer function omp_get_num_threads()
Возвращает количество потоков, используемых для выполнения параллельной секции.
int omp_get_max_threads(void); integer function omp_get_max_threads()
Возвращает максимальное количество потоков, которые можно использовать для выполнения параллельных секций программы.
int omp_get_thread_num(void); integer function omp_get_thread_num()
Возвращает идентификатор нити, из которой вызывается данная функция.
int omp_get_num_procs(void); integer function omp_get_num_procs()
Возвращает количество процессоров, доступных в данный момент программе.
int omp_in_parallel(void); logical function omp_in_parallel()
Возвращает значение true при вызове из активной параллельной секции программы.
void omp_set_dynamic(int threads); subroutine omp_set_dynamic(threads) logical threads
Включает или отключает возможность динамического назначения количества потоков при выполнении параллельной секции. По умолчанию эта возможность отключена.
int omp_get_dynamic(void); logical function omp_get_dynamic()
Возвращает значение true, если динамическое назначение количества потоков разрешено.
void omp_set_nested(int nested); subroutine omp_set_nested(nested) integer nested
Разрешает или запрещает вложенный параллелизм. По умолчанию вложенный параллелизм запрещен.
int omp_get_nested(void); logical function omp_get_nested()
Определяет, разрешен ли вложенный параллелизм.
Блокировки используются для предотвращения эффектов, приводящих к непредсказуемому (недетерминированному) поведению программы. Среди таких эффектов, например, гонки за данными, когда более чем один поток имеет доступ к одной и той же переменной.
void omp_init_lock(omp_lock_t *lock); subroutine omp_init_lock(lock) integer(kind = omp_lock_kind) :: lock
Инициализирует блокировку, связанную с идентификатором lock, для использования в последующих вызовах подпрограмм.
void omp_destroy_lock(omp_lock_t *lock); subroutine omp_destroy_lock(lock) integer(kind = omp_lock_kind) :: lock
Переводит блокировку, связанную с идентификатором lock, в состояние неопределенности.
void omp_set_lock(omp_lock_t *lock); subroutine omp_set_lock(lock) integer(kind = omp_lock_kind) lock
Переводит потоки из состояния выполнения в состояние ожидания до тех пор, пока блокировка, связанная с идентификатором lock, не окажется доступной. Поток становится владельцем доступной блокировки.
void omp_unset_lock(omp_lock_t *lock); subroutine omp_unset_lock(lock) integer(kind = omp_lock_kind) :: lock
После выполнения данного вызова поток перестает быть владельцем блокировки, связанной с идентификатором lock. Если поток не был владельцем блокировки, результат вызова не определен.
int omp_test_lock(omp_lock_t *lock); logical function omp_test_lock(lock) integer(kind = omp_lock_kind) :: lock
Возвращает значение "истина", если блокировка связана с идентификатором lock.
void omp_init_nest_lock(omp_nest_lock_t *lock); subroutine omp_init_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock
Инициализирует вложенную блокировку, связанную с идентификатором lock.
void omp_destroy_nest_lock(omp_nest_lock_t *lock); subroutine omp_destroy_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock
Переводит вложенную блокировку, связанную с идентификатором lock, в состояние неопределенности.
void omp_set_nest_lock(omp_nest_lock_t *lock); subroutine omp_set_nest_lock(lock) integer(kind= omp_nest_lock_kind) :: lock
Переводит выполняющиеся потоки в состояние ожидания до тех пор, пока вложенная блокировка, связанная с идентификатором lock, не станет доступной. Поток становится владельцем блокировки.
void omp_unset_nest_lock(omp_nest_lock_t *lock); subroutine omp_unset_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock
Освобождает выполняющийся поток от владения вложенной блокировкой, связанной с идентификатором lock. Если поток не был владельцем блокировки, результат не определен.
int omp_test_nest_lock(omp_nest_lock_t *lock); integer function omp_test_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock
Функция, позволяющая определить, связана ли вложенная блокировка с идентификатором lock . Если связана, возвращается значение счетчика, в противном случае возвращается значение 0.
Для профилирования программы можно использовать таймеры.
double omp_get_wtime(void); double precision function omp_get_wtime()
Возвращает время в секундах, прошедшее с произвольного момента в прошлом. Точка отсчета остается неизменной в течение всего времени выполнения программы.
double omp_get_wtick(void); double precision function omp_get_wtick()
Возвращает время в секундах, прошедшее между последовательными "тиками". Это время является мерой точности таймера.
Переменные окружения задаются следующим образом:
export ПЕРЕМЕННАЯ=значение (в среде UNIX )set ПЕРЕМЕННАЯ=значение (в среде Microsoft Windows )OMP_NUM_THREADS
Задает количество нитей при выполнении параллельных секций программы.
OMP_SCHEDULE
Задает способ распределения итераций циклов между нитями. Возможные значения:
static ;dynamic ;guidedКоличество итераций (необязательный параметр) указывается после одного из этих ключевых слов, отделяясь от него запятой, например:
export OMP_SCHEDULE="static, 10" OMP_DYNAMIC
Если этой переменной присвоено значение false, динамическое распределение итераций циклов между нитями запрещено, если true - разрешено.
OMP_NESTED
Если этой переменной присвоено значение false, вложенный параллелизм запрещен, если true - разрешен.
Параллельная -программа состоит из последовательных и параллельных секций. Границы параллельных секций обозначаются директивами . Процесс разработки -программы включает следующие этапы:
Профилирование может производиться как с помощью специальных программных инструментов, так и простыми средствами, например, с помощью вызова специальных подпрограмм-таймеров, размещенных в различных местах программы.
Цикл эффективно распараллеливается, если отсутствуют перекрестные зависимости между его итерациями. Избавиться от таких зависимостей иногда можно, выполнив преобразование цикла.
Необходимо правильно определить область видимости переменных в параллельных секциях программы. Параметр цикла, например, должен быть объявлен локальной переменной.
При вычислении суммы, например, к переменной, которая используется для "накопления" суммы, должна быть применена
Следует обратить внимание на синхронизацию вычислений. По умолчанию в циклах используется барьерная синхронизация. Наличие синхронизаций увеличивает предсказуемость поведения программы, но замедляет ее работу.
Дополнительный выигрыш в производительности дает объединение нескольких параллельных секций в одну. В этом случае уменьшаются накладные расходы на запуск нитей и их завершение.
Трансляция -программы выполняется со специальным ключом. В операционной системе Linux транслятор $$Intel\text{\textregistered} Compiler$$ использует ключ - , например:
#ifort -o my_prog prog_source.f9 0 -
В операционной системе $$Microsoft\text{\textregistered} Windows$$ командная строка выглядит следующим образом:
#ifort prog_source.f9 0 /Qopenmp
Приближенное вычисление интеграла:
$$I=\int\limits_{x_o}^{x_1}F(x)dx$$основано на его замене конечной суммой:
$$I_n=\sum\limits_{k=0}^n w_kF(x_k)$$где $$w_k$$ - числовые коэффициенты, а $$x_k$$ - точки отрезка $$[x_0, x_1]$$. Приближенное равенство:
$$I\approx I_n$$называется квадратурной формулой, точки $$x_k$$ - узлами квадратурной формулы, а числа $$w_k$$ - коэффициентами квадратурной формулы. Разные методы приближенного интегрирования отличаются выбором узлов и коэффициентов. От этого выбора зависит погрешность квадратурной формулы:
$$R_n=|I-I_n|$$Интегрирование методом трапеций - основано на использовании кусочно-линейного приближения для интегрируемой функции. Пусть $$F (x)$$ - гладкая функция на интервале $$[a,b]$$, и этот интервал делится на $$n$$ равных частей, каждая длиной $$h=\frac{b-a}{n}$$.
Приближение метода трапеций:
$$I(h)=\frac{h[f_0+2f_1+2f_2+\ldots+2f_{n-1}+f_n]}{2}$$где $$f_i = F (a + jh)$$ - значение интегрируемой функции в точке $$a + jh$$.
Идея трехточечного метода Симпсона заключается в следующем. Пусть $$x_m$$ - это средняя точка интервала $$[x_0, x_1]$$ и пусть $$Q (x)$$ - единственный полином второй степени, который интерполирует (приближает) подынтегральную функцию $$F(x)$$ по точкам $$x_0,$$ $$x_m$$ и $$x_1$$. Искомый интеграл аппроксимируется интегралом от функции $$Q (x)$$:
$$I_i\approx \int\limits_{x_i}^{x_{i+1}}Q(x)dx $$Эта оценка точна, если $$F (x)$$ является полиномом степени 3.
Обычно используются составные квадратурные формулы, когда промежуток интегрирования разбивается на $$N$$ подынтервалов и простая формула Симпсона применяется на каждом из этих подынтервалов:
$$I_i\approx \int\limits_{x_i}^{x_{i+1}}Q(x)dx $$$$I=\sum\limits_{i=1}^N I_i$$Недостатком рассмотренного метода является то, что он не дает возможности явно задать точность вычисления интеграла. Точность связана с количеством точек разбиения. От этого недостатка свободны методы интегрирования с адаптивным выбором шага разбиения. Если трехточечный
В заданиях лабораторной работы 1.1 предлагается выполнить распараллеливание последовательных программ, предназначенных для вычисления определенных интегралов. В задании 4 распараллеливание производится с помощью MPICH 1.2.7. Цель работы - получить навык анализа простых программ и выявления в них потенциального параллелизма,
применить для распараллеливания и MPI, сравнить трудоемкость обоих подходов и эффективность полученного результата. Звездочкой отмечено задание повышенной сложности.
Необходимый для выполнения данной лабораторной работы справочный материал можно найти на стр. 13 - 24 методического пособия "Средства программирования для многопроцессорных вычислительных систем".
Получить у преподавателя файл с исходным текстом программы (примеры 1, 2) и ознакомиться с реализацией квадратурной формулы.
Откомпилировать программу, выполнить расчет. Определить процессорное время, потраченное на выполнение расчета.
Проанализировать последовательный код и выявить участки потенциального параллелизма. Выполнить распараллеливание с помощью . Определить процессорное время, потраченное на выполнение расчета для разного числа потоков (меньшего, равного и большего, чем число процессоров). Сравнить с результатом, полученным в задании 2. Объяснить полученный результат.
Распараллелить программу с помощью MPI. Определить процессорное время, потраченное на выполнение расчета. Сравнить с результатами, полученными в заданиях 2
и 3.
На основании результатов, полученных при выполнении заданий данной лабораторной работы, написать отчет, в котором содержатся выводы об эффективности различных способов распараллеливания исходного последовательного кода и трудоемкости реализации этих способов на практике.
В программе на языке Fortran 90 реализован
program integral_trapez
integer, parameter :: div_no = 100
real, parameter :: x0 = 0., xl = 1. !3.14159
real, external :: F
real :: result
result = trapezium(F, x0, x1, div_no)
print *, result
end
real function trapezium(F, x0, x1, div_no)
real, external :: F
real, intent(in) :: x0, x1
integer, intent(in) :: div_no
real :: x, dx, sum integer :: j
dx = (x1 - x0) / div_no
sum = F(x0) + F(x1)
x = x0
do j = 1, div_no - 1
x = x + dx
sum = sum + 2.0 * F(x)
end do
trapezium = dx * sum / 2.0
end
real function F(x) real, intent(in) :: x !F= sin(x)
F = 4./(1.+x**2)
end
В программе на языке Fortran 90 реализован
program integral_simps
integer, parameter :: div_no = 100
real, parameter :: x0 = 0., x1 = 1. !3.14159
real, external :: F
real :: result
result = simpson(F, x0, x1, div_no) print *, result
end
real function simpson(F, x0, x1, div_no)
real, external :: F
real, intent(in) :: x0, x1
integer, intent(in) :: div_no
real :: x, dx, sum integer :: j
dx = (x1 - x0) / (2.0 * div_no)
sum = F(x0) + F(x1)
x = x0
do j = 1, 2 * div_no - 1
x = x + dx
if (mod(j, 2) /= 0) then
sum = sum + 4.0 * F(x)
else
sum = sum + 2.0 * F(x)
end if
end do
simpson = dx * sum / 3.0
end
real function F(x) real, intent(in) :: x !F= sin(x)
F = 4./(1.+x**2)
End
Параллельная -программа состоит из последовательных и параллельных секций. Границы параллельных секций обозначаются директивами . Процесс разработки -программы включает следующие этапы:
Профилирование может производиться как с помощью специальных программных инструментов, так и простыми средствами, например, с помощью вызова специальных подпрограмм-таймеров, размещенных в различных местах программы.
Цикл эффективно распараллеливается, если отсутствуют перекрестные зависимости между его итерациями. Избавиться от таких зависимостей иногда можно, выполнив преобразование цикла.
Необходимо правильно определить область видимости переменных в параллельных секциях программы. Параметр цикла, например, должен быть объявлен локальной переменной.
При вычислении суммы, например, к переменной, которая используется для "накопления" суммы, должна быть применена
Следует обратить внимание на синхронизацию вычислений. По умолчанию в циклах используется барьерная синхронизация. Наличие синхронизаций увеличивает предсказуемость поведения программы, но замедляет ее работу.
Дополнительный выигрыш в производительности дает объединение нескольких параллельных секций в одну. В этом случае уменьшаются накладные расходы на запуск нитей и их завершение.
Трансляция -программы выполняется со специальным ключом. В операционной системе Linux транслятор $$Intel\text{\textregistered} Compiler$$ использует ключ - , например:
#ifort -o my_prog prog_source.f9 0 -
В операционной системе $$Microsoft\text{\textregistered} Windows$$ командная строка выглядит следующим образом:
#ifort prog_source.f9 0 /Qopenmp
Классическим численным методом решения систем линейных алгебраических уравнений:
$$Ax=b$$где $$A=\|a_{ij}\|_{i,j=1,\ldots n}$$ - квадратная матрица коэффициентов, $$x$$ - вектор неизвестных, а $$b$$ - вектор правой части, является
Приведем описание алгоритма для
Очевидным условием применимости
В заданиях лабораторной работы 2.1 предлагается выполнить распараллеливание последовательной программы, предназначенной для решения систем линейных алгебраических уравнений. В задании 4 распараллеливание производится с помощью MPICH 1.2.7. Цель работы - получить навык анализа программ,
выявления в них участков потенциального параллелизма с наибольшей трудоемкостью, применить для распараллеливания и MPI, сравнить трудоемкость обоих подходов и эффективность полученного результата. Звездочкой отмечено задание повышенной сложности.
Необходимый для выполнения данной лабораторной работы справочный материал можно найти на стр. 13 - 24 методического пособия "Средства программирования для многопроцессорных вычислительных систем".
Получить у преподавателя файл с исходным текстом программы (пример 1) и ознакомиться с реализацией простого
Откомпилировать программу, выполнить расчет. Определить процессорное время, потраченное на выполнение расчета.
Проанализировать последовательный код. Выявить участки потенциального параллелизма с наибольшей трудоемкостью. Для этого следует подсчитать количество операций для прямого хода .
Определить процессорное время, потраченное на выполнение расчета для разного числа потоков (меньшего, равного и большего, чем число процессоров). Сравнить с результатом, полученным в задании 2. Объяснить полученный результат.
Распараллелить программу с помощью MPI. Определить процессорное время, потраченное на выполнение расчета. Сравнить с результатами, полученными в заданиях 2 и 3.
На основании результатов, полученных при выполнении заданий данной лабораторной работы, написать отчет, в котором содержатся выводы об эффективности различных способов распараллеливания исходного последовательного кода и трудоемкости реализации этих способов на практике.
В программе на языке Fortran 90 реализован простой
program linear_algebra_gauss
integer, parameter :: n = 3
real, dimension(1:n, 1:n) :: a, left
real, dimension(1:n) :: x, b
data left/9 * 0./
data a/.471, 4.27, .012, 3.21, -.513, 1.273, -1.307, 1.102, -
4.175 /
data b/ 2.425, -.176, 1.423 /
! Решение: 0.07535443 0.6915624 -0.1297573
! Прямой ход метода Гаусса
do k = 1, n - 1
do i = k + 1, n
left(i, k) = a(i, k) / a(k, k)
b(i) = b(i) - left(i, k) * b(k)
end do
do j = k + 1, n
do i = k + 1, n
a(j, i) = a(j, i) - left(j, k) * a(k, i)
end do
end do
end do
! Обратная подстановка x(n) = b(n) / a(n, n)
do i = n - 1, 1, -1
x(i) = b(i)
do j = i + 1, n
x(i) = x(i) - a(i, j) * x(j)
end do
x(i) = x(i) / a(i, i)
end do
print *, (x(i), i = 1, n)
end
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.