Основы параллельного программирования вычислительных систем с распределенной и разделяемой памятью

Автор работы: Пользователь скрыл имя, 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

Вложенные файлы: 1 файл

Cluster.doc

— 829.00 Кб (Скачать файл)
3.1.1 MPI-коммуникация типа «точка-точка»

В MPI все коммуникационные операции выполняются с использованием коммуникаторов. Коммуникатор представляет коммуникационную область, которая, по сути, является набором процессов, обменивающихся сообщениями друг с другом. В данном разделе предполагается, что для коммуникаций используется коммуникатор MPI_COMM_WORLD, являющийся MPI-коммуникатором по умолчанию. Данный коммуникатор включает в себя все процессы, выполняющие программу.

Самая простая форма обмена данными между процессами обеспечивается коммуникацией типа «точка-точка». В данной коммуникационной операции принимают участие два процесса: передающий процесс выполняет операцию передачи, принимающий процесс выполняет соответствующую операцию приема.

Операция передачи – блокирующая  операция:

int MPI_Send(void *smessage,

  int count,

  MPI_Datatype datatype,

  int dest,

  int tag,

  MPI_Comm comm)

Здесь:

  • smessage определяет буфер передатчика, содержащий данные, которые должны быть переданы последовательно;
  • count – число элементов, которые необходимо послать из буфера передатчика;
  • datatype – тип данных каждого элемента в буфере передатчика; все элементы имеют один и тот же тип данных;
  • dest определяет ранг процесса, который должен получить данные; каждый процесс коммуникатора имеет уникальный ранг; ранг может принимать значение от нуля до количества процессов без единицы;
  • tag идентификатор сообщения, который может быть использован приемником для того, чтобы различать различные сообщения от одного и того же передатчика;
  • 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)

Параметры имеют следующее  значение:

  • rmessage определяет принимающий буфер, в котором должно быть сохранено сообщение;
  • count – максимальное число элементов, которые должны быть получены;
  • datatype – тип данных получаемых элементов;
  • source – ранг передающего процесса;
  • tag – идентификатор сообщения;
  • comm – коммуникатор, используемый для коммуникации;
  • 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_SOURCE определяет ранг процесса-отправителя;
  • status.MPI_TAG определяет идентификатор полученного сообщения;
  • status.MPI_ERROR содержит код ошибки.

Структура данных status также содержит информацию о длине полученного сообщения. Эту длину можно узнать, вызвав MPI-функцию

int MPI_Get_count (MPI_Status *status,

   MPI_Datatype datatype,

   int *count_ptr)

Здесь status – указатель на структуру данных status,возвращенную функцией MPI Recv(). Функция возвращает количество полученных элементов в переменной, на которую указывает count_ptr.

Передача сообщения в MPI обычно происходит в три этапа:

1) элементы данных, которые нужно  отправить, копируются из буфера  отправителя smessage в системный буфер исполняющей системы MPI. Сообщение дополняется заголовком с информацией о процессе-отправителе, процессе-получателе, идентификаторе и используемом коммуникаторе;

2) сообщение посылается по сети  от процесса-отправителя к процессу-получателю;

3) на принимающей стороне входные  данные сообщения копируются  из системного буфера в буфер rmessage,определенный функцией MPI_Recv().

Обе функции 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.

3.1.2 Тупиковые ситуации при коммуникациях типа «точка-точка»

Использовать операции передачи и приема нужно аккуратно, во избежание  тупиковых ситуаций. Рассмотрим следующий пример, демонстрирующий этот негативный эффект.

// фрагмент программы, приводящий к тупиковой ситуации

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)

Данная операция является блокирующей и объединяет операции передачи и приема.

Параметры имеют следующее  значение:

  • sendbuf определяет буфер отправителя, в котором сохраняются отправляемые элементы данных;
  • sendcount – количество отправляемых элементов;
  • sendtype – тип данных элементов, находящихся в буфере отправителя;
  • dest – ранг процесса-приемника данных;
  • sendtag – дескриптор отправляемого сообщения;
  • recvbuf – буфер получателя, в котором должно быть сохранено принимаемое сообщение;
  • recvcount – максимальное количество принимаемых элементов;
  • recvtype – тип данных принимаемых элементов;
  • source – ранг процесса, от которого ожидается сообщение;
  • recvtag – ожидаемый дескриптор получаемого сообщения;
  • comm – коммуникатор, используемый для коммуникации;
  • status определяет структуру данных для хранения информации о получаемом сообщении.

Информация о работе Основы параллельного программирования вычислительных систем с распределенной и разделяемой памятью