- •1.3.2. Прикладная математика и информатика
- •2. Проблемы, связанные с использованием параллельных алгоритмов
- •3. Примеры и описание технологий параллельного программирования
- •3.1. Технологии для разработки параллельных программ для систем с общей и распределенной памятью.
- •3.1.1. Модель передачи сообщений. Mpi.
- •3.2. Технологии разработки параллельных программ для графических процессоров
- •4. Средства анализа технологий параллельного программирования.
- •4.1. Комплексный подход к анализу эффективности программ для параллельных вычислительных систем
- •4.2. Типы анализа производительности
- •5.Анализ производительности технологий mpi и OpenMp
3.1. Технологии для разработки параллельных программ для систем с общей и распределенной памятью.
Параллельные вычислительные модели образуют сложную структуру. Они могут быть дифференцированы по нескольким направлениям: является ли память физически общей или распределенной; насколько обмен данными реализован в аппаратном и насколько в программном обеспечении; что в точности является единицей исполнения и т.д. Общая картина осложняется тем, что программное обеспечение позволяет реализовать любую вычислительную модель на любом оборудовании.
Для систем с распределенной памятью организация параллельных вычислений является возможной при использовании тех или иных способов передачи данных между взаимодействующими процессорами. Модель с передачей сообщений подразумевает множество процессов, имеющих только локальную память, но способных связываться с другими процессами, посылая и принимая сообщения. Определяющим свойством модели для передачи сообщений является тот факт, что передача данных из локальной памяти одного процесса в локальную память другого процесса требует выполнения операций обоими процессами.
Технологии с передачей данных имеют ряд преимуществ, таких как:
Универсальность. Модель с передачей сообщений хорошо подходит к системам с отдельными процессорами, соединенными с помощью (быстрой или медленной) сети. Тем самым, она соответствует как большинству сегодняшних параллельных суперкомпьютеров, так и сетям рабочих станций.
Выразительность. Модель с передачей сообщений является полезной и полной для выражения параллельных алгоритмов. Она приводит к ограничению управляющей роли моделей, основанных на компиляторах и параллельности данных. Она хорошо приспособлена для реализации адаптивных алгоритмов, подстраивающихся к несбалансированности скоростей процессов.
Легкость отладки. Отладка параллельных программ обычно представляет собой непростую задачу. Модель с передачей сообщений управляет обращением к памяти более явно, чем любая другая модель(только один процесс имеет прямой доступ к любым переменным в памяти), и, тем самым, облегчает локализацию ошибочного чтения или записи в память.
Производительность. Важнейшей причиной того, что передача сообщений остается постоянной частью параллельных вычислений, является производительность. По мере того, как CPU становятся все более быстрыми, управление их кэшем и иерархией памяти вообще становится ключом к достижению максимальной производительности. Передача сообщений дает программисту возможность явно связывать специфические данные с процессом, что позволяет компилятору и схемам управления кэшем работать в полную силу
Предпочтительность использования той или иной технологии определяют следующие факторы:
Легкость разработки и сопровождения параллельных программ;
Эффективность выполнения параллельных программ;
Переносимость и повторное использование параллельных программ.
Рассмотрим технологии MPI и OpenMP.
3.1.1. Модель передачи сообщений. Mpi.
MPI – программы интерфейса передачи сообщений. Смысл технологии заключается в том, что все процессы обращаются друг с другом способом подачи и получения сообщений. Преимущество данной модели передачи сообщений – это интерфейс, для работы с которым не критична слабая аппаратная составляющая компьютера, а нужна лишь единая сеть между процессорами для передачи сообщений среди любых двух компьютеров. Благодаря данному подходу достигается работа на любой параллельной системе.
В модели передачи сообщений, за каждым из множества процессов параллельной программы числится индивидуальное локальное адресное пространство.
Среди основных преимуществ интерфейса передачи сообщений сопоставив с другими интерфейсами связи, выделяются данные пункты:
реально использование языками ФОРТОРАН, C, C ++;
предусматривается одновременный процесс обмена сообщений и вычислений;
предусматриваются режимы обмена сообщениями, избегая ненужного копирования информации в буфер обмена;
широкий спектр совместных операций (например, передача информации, сбор информации из различных процессоров), что позволяет гораздо более эффективно реализовать работу программы, чем внедрение использования ошибочной последовательности адресования точка-точка;
широкий спектр операций приведения (например, сложение данных находящихся на разных процессорах, или поиск их максимального или минимального значения), которые в свою очередь делают возможным белее эффективную реализации, когда программист не располагает информацией о параметрах системы связи;
реализована достойная система обозначения получателей сообщений, благодаря которой не тратится время на деление программы на блоки;
можно указать тип передаваемой информации, что позволяет автоматически его преобразовать в случае различий в данных.
Интерфейс передачи сообщений инструмент программирования низкого уровня. Модель, созданная путем отправки сообщения, имеет набор библиотек, направленных на численные методы (например, для решения систем линейных уравнений, обращения матриц и подобное). Интерфейс передачи сообщений не имеет механизмы для начального размещения задач по процессорам; размещение задается программистом вручную.
Увеличение эффективности параллельных программ и возможность свести к минимуму расходы организации параллелизма – основное требование к модели обмена сообщениями, в качестве интерфейса связи.
Пример, умножения матрицы на вектор:
#include <stdio.h>
#include <stdlib.h>
#include "mpi.h"
int main (int argc, char* argv[])
{
int procs_rank, procs_count;
int i, j, n = 1000, local_n;
double *local_a, *b, *local_с, *a, *с;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &procs_count);
MPI_Comm_rank(MPI_COMM_WORLD, &procs_rank);
local_n = n / procs_count;
local_a = (double *) malloc((local_n * n) * sizeof(double));
b = (double *) malloc(n * sizeof(double));
local_с = (double *) malloc(local_n * sizeof(double));
с = (double *) malloc(n * sizeof(double));
a = (double *) malloc((n * n) * sizeof(double));
if (procs_rank == 0)
{
for (i=0; i<n; i++)
for (j=0; j<n; j++)
a[i*n+j]=rand();
for (i=0; i<n; i++)
b[i] = rand();
}
MPI_Bcast(b, n, MPI_DOUBLE, 0, MPI_COMM_WORLD);
MPI_Scatter(a, n * local_n, MPI_DOUBLE, local_a, n * local_n, MPI_DOUBLE, 0, MPI_COMM_WORLD);
for (i = 0; i < local_n; i++)
for (j = 0; j < n; j++)
local_с[i] += local_a[i*n+j] * b[j];
MPI_Gather(local_с, local_n, MPI_DOUBLE, с, local_n, MPI_DOUBLE, 0, MPI_COMM_WORLD);
if (procs_rank == 0)
{
for (i=0; i<n; i++)
printf("%f3.3 \n",с[i]);
}
MPI_Finalize();
return 0;
}
3.1.2 OpenMP
OpenMP – технология, которая позволяет писать распараллеленные программы для многопроцессорных вычислительных систем с общей оперативной памятью. Программа представлена в виде набора нитей, объединяемых общей памятью.
OpenMP реализует параллельные вычисления с помощью многопоточности, в которой «главный» поток создает набор подчиненных потоков, а задача разделяется между ними. При этом, потоки выполняются параллельно на машине с несколькими процессорами. Все задачи разделённые на потоки реализуются параллельно. Множеством потоков можно управлять как изнутри с помощью библиотечных процедур, так и снаружи, с помощью переменных окружения.
OpenMP довольно проста в понимании и реализации, оно состоит из двух основных типов: команд прагм и методов выполняющих в среде используемой OpenMP. Прагма это указатель который сообщает компилятору реализацию параллельной работы блоков кода. Каждая команда начинаются с pragma omp. В случае, если технология не поддерживается компилятором, то команда просто игнорируется. Каждая команда может иметь несколько индивидуальных свойств. Чтобы назначить класс переменных, атрибуты указываются отдельно, так как они могут использоваться различными директивами.
OpenMP это модель «ветвления-слияния». Когда начинается процесс, то он называется нитью или главным потоком. Эта нить выполняется последовательно, пока не будет использована команда на распараллеливание, она разделит процесс на множество параллельно выполняемых задач. Все множество нитей выполняет определённую задачу, связанную с параллельной областью. После выполнения своих задач все нити заканчивают работу и собираются в один поток, который дальше выполняется последовательно. Поток можно делить на разное количество нитей. Команды на распараллеливание программы можно использовать сколько угодно на всех участках программы.
Распараллеленная задача может повторно быть распараллелена. Если данный метод запрещен или не поддерживается, вложенное распараллеливание всегда будет состоять лишь из одного потока. После деления потока на нити, поставленная задача совместно выполняется всеми нитями, то есть каждая нить не выполняет задачу полностью сама.
Различными библиотеками реализованы методы, позволяющие обращаться к переменным находящимся в различных нитях. Также, стоит заметить, что мы наверняка не знаем будут ли доступны доступы к данным в различных нитях при реализации ввода и вывода. Отсюда следует, что данный вопрос требует отслеживания и решения со стороны программиста.
OpenMP требует, чтобы программа была запущена на системе с разделяемой памятью, которая в свою очередь хранит переменные необходимые для работы в нитях. Заметим, что у каждой нити имеется память предназначенная только для неё самой, она называется частной памятью.
При распараллеливании память, используемая в параллельной части, делится на разделяемую и частную. Все переменные находящиеся в параллельной части, имеют переменную, которая хранится не в параллельной части. При делении потока на нити, каждая нить имеет копии переменных, которые хранящихся в потоке и при этом каждая переменная потока при разделении его на нить, ссылается на одну и ту же память. Все переменные дополнительно созданные в нити ссылаются на личную память.
Кроме того, каждый поток может иметь своевременное представление памяти. Данный метод не есть обязательной частью модели памяти OpenMP, он дает возможность сохранять переменные в кэше, что позволяет не обращаться лишний раз к памяти. OpenMP обеспечено средствами, чтобы заставить установить временное представление с памятью.
Для того, чтобы пользоваться механизмами OpenMP необходимо пользоваться компиляторами поддерживающими стандарт OpenMP, при этом необходимо указывать ключ, например:
icc/ifort используется ключ компилятора -openmp gcc /gfortran используется ключ компилятора -fopenmp Sun Studio используется ключ компилятора -xopenmp Visual C++ используется ключ компилятора - /openmp
Компилятор читает команды OpenMP и в последствии параллелизирует код программы. Если OpenMP не поддерживается компилятором, то она просто игнорируется. Компиляторы, которые поддерживают OpenMP имеют макрос _OPENMP, облегчающий процесс распараллеливания, его используют для условной компиляции определённых частей, относящихся к параллельной части программы. Это определение может быть проверено следующим образом:
#ifdef _OPENMP fn(); #endif
Пример 1.
Параллельное вычисление числа Пи с применением OpenMP:
#include <stdio.h> #include <math.h> #include <omp.h> int main() { const int N = 10000000; const double L = 1.0; const double h = L / N; const double x_0 = 0.0; double pi; double t_1, t_2; int i; double sum = 0.0; t_1 = omp_get_wtime();
#pragma omp parallel for reduction(+: sum) schedule(static) for (i = 0; i < N; ++i) { double x = x_0 + i * h + h/2; sum += sqrt(1 - x*x); }
t_2 = omp_get_wtime(); pi = sum * h * 4.0; printf("omp_get_max_threads(): %d\n", omp_get_max_threads()); printf("time: %f\n", t_2 - t_1); printf("pi ~ %f\n", pi); return 0; }
Пример 2.
Умножение двух квадратных матриц.
#include <stdio.h> #include <omp.h> #define N 4096 double a[N][N], b[N][N], c[N][N]; int main() { int i, j, k; double t1, t2;
for (i = 0; i < N; i++) for (j = 0; j < N; j++) a[i][j] = b[i][j] = i * j; t1=omp_get_wtime();
#pragma omp parallel for shared(a, b, c) private(i, j, k) for(i = 0; i < N; i++){ for(j = 0; j < N; j++){ c[i][j] = 0.0; for(k = 0; k < N; k++) c[i][j] += a[i][k] * b[k][j]; } } t2=omp_get_wtime(); printf("Time=%lf\n", t2-t1); }
