Автор работы: Пользователь скрыл имя, 05 Июня 2013 в 22:25, курсовая работа
В настоящее время проводится много исследований в области языков и сред параллельного программирования, целью которых является содействие параллельному программированию путем обеспечения поддержки на должном уровне абстракции. Однако, много эффективных технологий доступно уже сейчас.
Введение 4
1 Общие представления о параллельном программировании 6
2 Архитектура параллельных компьютеров 8
2.1 Развитие архитектуры компьютера 8
2.2 Организация памяти параллельных компьютеров 10
2.3 Сети межсоединений 15
2.3.1 Свойства сетей межсоединений. Статические сети межсоединений 16
2.3.2 Динамические сети межсоединений 19
3 Введение в программирование с использованием передачи сообщений 22
3.1 Введение в MPI 22
3.1.1 MPI-коммуникация типа «точка-точка» 25
3.1.2 Тупиковые ситуации при коммуникациях типа «точка-точка» 30
3.1.3 Неблокирующие операции и режимы коммуникации 33
3.1.4 Коммуникационный режим 35
3.2 Групповые коммуникационные операции 36
3.3 Группы процессов и коммуникаторы 41
3.3.1 Группы процессов в MPI 41
3.3.2 Топологии процессов 45
3.3.3 Временные и прерывающие процессы 49
4 Введение в потоковое программирование в OpenMP 50
4.1 Проблемы поточной обработки цикла 52
4.2 Условия гонок 52
4.3 Управление общими и приватными данными 53
4.4 Планирование и разбиение циклов 55
4.5 Библиотечные функции ОреnМР 57
4.6 Отладка 58
4.7 Производительность 59
4.8 Основные моменты 61
5 Протокол сеансового уровня SSH 63
6 Удаленный вход на кластер 66
7 Операционная система Linux 68
7.1 Интерфейс ОС Linux 68
7.2 Некоторые команды Linux 68
8 Компилирование последовательных программ (Fortran/C/C++) 71
9 Основные команды 72
10 Работа с кластером 74
Список использованных источников информации 83
В MPI все коммуникационные операции выполняются с использованием коммуникаторов. Коммуникатор представляет коммуникационную область, которая, по сути, является набором процессов, обменивающихся сообщениями друг с другом. В данном разделе предполагается, что для коммуникаций используется коммуникатор MPI_COMM_WORLD, являющийся MPI-коммуникатором по умолчанию. Данный коммуникатор включает в себя все процессы, выполняющие программу.
Самая простая форма обмена
данными между процессами обеспечивается
коммуникацией типа «точка-точка».
В данной коммуникационной операции
принимают участие два
Операция передачи – блокирующая операция:
int MPI_Send(void *smessage,
int count,
MPI_Datatype datatype,
int dest,
int tag,
MPI_Comm comm)
Здесь:
Размер сообщения в байтах может быть вычислен путем умножения числа count на число байт, используемых для типа datatype. tag – целое число в пределах от 0 до 32 767.
Для получения сообщения процесс выполняет следующую блокирующую операцию:
int MPI_Recv(void *rmessage,
int count,
MPI_Datatype datatype,
int source,
int tag,
MPI_Comm comm,
MPI_Status *status)
Параметры имеют следующее значение:
Предопределенные типы данных MPI и соответствующие типы данных C показаны в таблице 2.
Таблица 2 – Предопределенные типы данных MPI
Тип данных MPI |
Тип данных C |
MPI_CHAR |
signed char |
MPI_SHORT |
signed short int |
MPI_INT |
signed int |
MPI_LONG |
signed long int |
MPI_LONG_LONG_INT |
long long int |
MPI_UNSIGNED_CHAR |
unsigned char |
MPI_UNSIGNED_SHORT |
unsigned short int |
MPI_UNSIGNED |
unsigned int |
MPI_UNSIGNED_LONG |
unsigned long int |
MPI_UNSIGNED_LONG_LONG |
unsigned long long int |
MPI_FLOAT |
float |
MPI_DOUBLE |
double |
MPI_LONG_DOUBLE |
long double |
MPI_WCHAR |
wide char |
MPI_PACKED |
– |
MPI_BYTE |
– |
Если указать source = MPI_ANY_SOURCE, то процесс сможет получать сообщения от любого процесса. Аналогично, tag = MPI_ANY_TAG позволит процессу получать сообщения с любым идентификатором. В обоих случаях структура данных status содержит информацию о том, какой процесс послал полученное сообщение, и какой идентификатор был использован отправителем. После завершения MPI Recv() структура status содержит следующие данные:
Структура данных status также содержит информацию о длине полученного сообщения. Эту длину можно узнать, вызвав MPI-функцию
int MPI_Get_count (MPI_Status *status,
MPI_Datatype datatype,
int *count_ptr)
Здесь status – указатель на структуру данных status,возвращенную функцией MPI Recv(). Функция возвращает количество полученных элементов в переменной, на которую указывает count_ptr.
Передача сообщения в MPI обычно происходит в три этапа:
1) элементы данных, которые нужно
отправить, копируются из
2) сообщение посылается по сети
от процесса-отправителя к
3) на принимающей стороне
Обе функции MPI_Send() и MPI_Recv() являются блокирующими асинхронными операциями. Это означает, что операция MPI_Recv() может быть запущена в момент, когда соответствующая операция MPI_Send() еще не была запущена. Процесс, выполняющий операцию MPI_Recv(), заблокирован, пока определенный буфер получателя содержит отправленные данные. Аналогично, MPI_Send() также может быть запущена, когда соответствующая операция MPI_Recv() еще не была запущена. Процесс, выполняющий MPI_Send(), будет заблокирован до того момента, когда определенный буфер отправителя сможет быть использован заново.
Пример. Рассмотрим MPI-программу, в которой процесс с рангом 0 использует MPI_Send() для отправки сообщения процессу с рангом 1 (второй процесс использует MPI_Recv() для приема сообщения). Функция MPI_Init() должна быть вызвана до вызова какой-либо другой MPI-функции с целью инициализации исполняющей системы MPI. Вызов функции MPI_Comm_rank (MPI_COMM_WORLD, &my_rank) возвращает в переменной my_rank ранг вызывающего процесса, находящего в определенном коммуникаторе MPI_COMM_WORLD. Фукция MPI_Comm_size (MPI_COMM_WORLD, &p) возвращает в переменной p количество процессов в определенном коммуникаторе. В данном примере различные процессы исполняют различные части программы в зависимости от их ранга, хранящего в переменной my_rank: процесс 0 выполняет копирование строки и функцию MPI_Send(), а процесс 1 выполняет соответствующую операцию MPI_Recv(). MPI_Finalize() должна быть последней операцией любой MPI-программы.
#include <stdio.h>
#include <string.h>
#include "mpi.h"
int main (int argc, char *argv[])
{ int my_rank, p, tag=0;
char msg[20];
MPI_Status status;
MPI_Init (&argc, &argv);
MPI_Comm_rank (MPI_COMM_WORLD, &my_rank);
MPI_Comm_size (MPI_COMM_WORLD, &p);
if (my_rank == 0)
{ strcpy(msg, "Hello");
MPI_Send (msg, strlen(msg)+1, MPI_CHAR, 1, tag, MPI_COMM_WORLD);
}
if (my_rank == 1)
MPI_Recv (msg, 20, MPI_CHAR, 0, tag, MPI_COMM_WORLD, &status);
MPI_Finalize();
}
Каждая MPI-библиотека должна обладать следующим свойством: доставка сообщений происходит в том же порядке, в котором они были отправлены. Однако при участии более двух процессов такой порядок может быть нарушен. Рассмотрим пример, демонстрирующий порядок операций приема.
MPI_Comm_rank (comm, &my_rank);
if (my_rank == 0) {
MPI_Send (sendbuf1, count, MPI_INT, 2, tag, comm);
MPI_Send (sendbuf2, count, MPI_INT, 1, tag, comm);
}
else if (my_rank == 1) {
MPI_Recv (recvbuf1, count, MPI_INT, 0, tag, comm, &status);
MPI_Send (recvbuf1, count, MPI_INT, 2, tag, comm);
}
else if (my_rank == 2) {
MPI_Recv (recvbuf1, count, MPI_INT, MPI_ANY_SOURCE, tag, comm, &status);
MPI_Recv (recvbuf2, count, MPI_INT, MPI_ANY_SOURCE, tag, comm, &status);
}
Процесс 0 сначала посылает сообщение процессу 2, а затем – процессу 1. Процесс 1 получает сообщение от процесса 0 и отправляет его процессу 2. Процесс 2 получает два сообщения в том порядке, в котором они приходят, используя MPI_ANY_SOURCE. В таком случае можно ожидать, что процесс 2 сначала получит сообщение, которое было послано ему процессом 0 напрямую, так как процесс 0 посылает это сообщение первым и так как второе сообщение, отправленное процессом 0, должно быть переадресовано процессом 1 перед его получением процессом 2. Однако реальная ситуация может быть иной, поскольку первое сообщение, отправленное процессом 0, могло быть задержано из-за коллизии в сети, в то время как второе сообщение, отправленное процессом 0, могло дойти без задержки. Таким образом, процесс 2 может первым получить сообщение, которое было отправлено процессом 0 и переадресовано процессом 1. Следовательно, если в обмене сообщениями участвуют более двух процессов, то нет гарантии какого-либо порядка доставки сообщений. В данном примере, обеспечить ожидаемый порядок приема сообщений процессом 2 можно, определив ожидаемого отправителя вместо MPI_ANY_SOURCE.
Использовать операции передачи и приема нужно аккуратно, во избежание тупиковых ситуаций. Рассмотрим следующий пример, демонстрирующий этот негативный эффект.
// фрагмент программы, приводящий к тупиковой ситуации
MPI_Comm_rank (comm, &my_rank);
if (my_rank == 0) {
MPI_Recv (recvbuf, count, MPI_INT, 1, tag, comm, &status);
MPI_Send (sendbuf, count, MPI_INT, 1, tag, comm);
}
else if (my_rank == 1) {
MPI_Recv (recvbuf, count, MPI_INT, 0, tag, comm, &status);
MPI_Send (sendbuf, count, MPI_INT, 0, tag, comm);
}
Оба процесса 0 и 1 исполняют операцию MPI_Recv() до операции MPI_Send(). Это приводит к тупиковой ситуации из-за взаимного ожидания: для процесса 0 исполнение операции MPI_Send() не может быть начато до окончания исполнения предшествующей операции MPI_Recv(), возможного в случае выполнения процессом 1 операции MPI_Send(). Но это не может произойти, так как процесс 1 также должен сначала завершить операцию MPI_Recv(), а это может случиться только если процесс 0 выполнит свою операцию MPI_Send().
Возникновение тупиковой ситуации также может зависеть от того, использует ли исполняющая система внутренние системные буферы или нет. Рассмотрим пример.
MPI_Comm_rank (comm, &my_rank);
if (my_rank == 0) {
MPI_Send (sendbuf, count, MPI_INT, 1, tag, comm);
MPI_Recv (recvbuf, count, MPI_INT, 1, tag, comm, &status);
}
else if (my_rank == 1) {
MPI_Send (sendbuf, count, MPI_INT, 0, tag, comm);
MPI_Recv (recvbuf, count, MPI_INT, 0, tag, comm, &status);
}
Передача сообщений в данном случае выполнится корректно, если исполняющая система MPI использует системные буферы. Тогда сообщения, отправленные процессами 0 и 1, копируются из определенного буфера sendbuf в системный буфер до начала передачи. После осуществления такой операции копирования, операция MPI_Send() является завершенной, так как буферы отправителя могут быть использованы заново. Таким образом, каждый процесс может выполнить свою операцию MPI_Recv(), и взаимоблокировки не возникнет. Однако тупиковая ситуация возникнет, если исполнительная система не использует системные буферы, либо если эти буферы слишком малы.
Безопасное исполнение (даже в случае отсутствия системных буферов) выглядит следующим образом:
//фрагмент программы, не приводящий к тупиковой ситуации
MPI_Comm_rank (comm, &my_rank);
if (my_rank == 0) {
MPI_Send (sendbuf, count, MPI_INT, 1, tag, comm);
MPI_Recv (recvbuf, count, MPI_INT, 1, tag, comm, &status);
}
else if (my_rank == 1) {
MPI_Recv (recvbuf, count, MPI_INT, 0, tag, comm, &status);
MPI_Send (sendbuf, count, MPI_INT, 0, tag, comm);
}
Под безопасной понимается программа, корректность которой не зависит от предположений о каких-либо определенных свойствах исполнительной системы MPI (например, таких как существование системных буферов или их размер).
Во многих случаях процессы как отправляют, так и принимают данные. Для поддержки такого поведения MPI содержит следующие функции.
int MPI_Sendrecv (void *sendbuf,
int sendcount,
MPI_Datatype sendtype,
int dest,
int sendtag,
void *recvbuf,
int recvcount,
MPI_Datatype recvtype,
int source,
int recvtag,
MPI Comm comm,
MPI Status *status)
Данная операция является блокирующей и объединяет операции передачи и приема.
Параметры имеют следующее значение: