Параллельный Процессинг в ОС Linux с использованием систем PVM и MPI

  Автор: © Rahul U. Joshi
Перевод: © Роман Шумихин.


  Цель этой статьи - вкратце рассказать читателю о PVM и MPI, двух широко используемых программных системах для разработки программ параллельного обмена сообщениями. Они позволяют нам использовать группу гетерогенных компьютеров на базе ОС UNIX/LINUX, объединённых сетью, как отдельную машину для решения большой задачи.

1. Введение в параллельный процессинг

Параллельный процессинг - это форма компьютинга, в которой некоторое число процессов выполняются одновременно, так что время, требуемое для решения задачи, сокращается. Раньше параллельный процессинг использовался для крупномасштабных симуляций (например, молекулярных симуляций, симуляции взрыва атомной бомбы и т.д.), решения числовых задач большого объёма и задач обработки данных (например, обработка результатов переписи населения). Как бы то ни было, с быстрым падением цен на компьютерное "железо" параллельный процессинг всё чаще используется для решения более рутинных задач. Многопроцессорные серверы существуют уже достаточно давно. Кстати, параллельный процессинг тоже используется и в вашем компьютере. Например, графический процессор работает совместно с основным процессором, чтобы отображать графику на вашем мониторе, это тоже форма параллельного процессинга.

Но, кроме аппаратных средств, для параллельного процессинга требуется ещё и программная поддержка, которая даст нам возможность параллельно запускать программы и координировать их исполнение. Такая координация необходима из-за зависимостей параллельных программ друг от друга. Вы поймёте, что к чему, когда мы доберёмся до примера. Самый широко используемый метод добиться такой координации - это обмен сообщениями (message passing) , при котором программы координируют своё выполнение и общаются между собой, посылая сообщения одна другой. Так, например, программа может сказать другой программе "Ok! Вот промежуточный результат, который нужен тебе для продолжения твоего выполнения". Если всё это звучит слишком абстрактно, давайте разберём очень простой пример.

2. Очень простая задача

В этом разделе мы рассмотрим очень простую задачу и то, как можно использовать параллельный процессинг, чтобы ускорить процесс нахождения её решения. Задача состоит в нахождении суммы некоторого количества целых чисел, хранящихся в массиве. Предположим, в массиве items хранится 100 целых чисел. Как же нам распараллелить программу? Сначала мы должны придумать способ, которым можно решить эту задачу, используя несколько программ, работающих одновременно. Распараллеливание становится довольно трудной задачей в связи с зависимостью одних данных от других. Например, если вы хотите вычислить (a + b) * c, что включает в себя 2 операции, то мы не можем выполнять их одновременно: сложение должно быть выполнено до умножения. К счастью, задача, которую мы выбрали, легко распараллеливается. Предположим, что 4 программы или процессора будут одновременно решать задачу сложения. Самым простым способом решения задачи было бы разбить массив items на 4 части и заставить каждую программу обрабатывать свою часть. Распараллеливание этой программы будет выглядеть так:

  1. Четыре программы, скажем, P0, P1, P2 и P3 будут решать задачу.
  2. P0 найдёт сумму элементов массива от items[0] до items[24]. P1 таким же образом найдёт сумму от items[25] до items[49], P2 от items[50] до items[74] и P3 от items[75] до items[99].
  3. После выполнения этих программ должна быть запущена другая программа, которая найдёт сумму 4-х полученных результатов и выдаст окончательный ответ. Поскольку элементы массива items неизвестны нашим четырём программам (P0 - P3), должна быть ещё программа, которая передаст этим программам значения элементов. Таким образом, кроме программ P0 - P3, нам требуется программа, которая будет распределять данные, собирать результаты и координировать исполнение. Мы назовём такую программу master (хозяин), а программы P0 - P3 slaves (рабы). Такая организация будет называться отношением хозяин - раб (master - slave paradigm).

Помня о сказанном выше, давайте напишем алгоритм для программы master и для программ slave.


/* Алгоритм для программы master */
инициализируем массив `items'.

/* посылаем данные программам slave */
for i = 0 to 3
    Посылаем элементы, начиная с items[25*i] до items[25*(i+1)-1], программе slave Pi
end for

/* собираем результаты от программ slave */
for i = 0 to 3
    Получаем результат от программы slave Pi и помещаем его в массив result[i]
end for

/* вычисляем окончательный результат */
sum = 0
for i = 0 to 3
    sum = sum + result[i]
end for

print sum

Алгоритм slave может быть написан так.
/* Алгоритм для программы slave */

Получить 25 элементов от программы master в какой-нибудь массив, скажем, `items'

/* вычислить промежуточный результат */
sum = 0
for i = 0 to 24
    sum = sum + items[i]
end for

послать `sum' как промежуточный результат программе master

3. Реализация на PVM

Теперь, когда мы разработали основной алгоритм, давайте попробуем его реализовать. На каком железе мы будем запускать нашу программу? Ясно, что только немногие из нас имеют доступ к машинам, специально созданным для запуска параллельных программ. Однако, нам не нужно никакого специального оборудования чтобы реализовать эту программу. Нам будет достаточно одного компьютера или группы соединённых между собой компьютеров. И это всё благодаря PVM, программной системе, которая позволяет нам использовать соединённые между собой компьютеры для параллельного выполнения программ. PVM расшифровывается как Параллельная Виртуальная Машина (Parallel Virtual Machine). Она позволяет создавать несколько программ или процессов, которые выполняются одновременно на одной или разных машинах, и предоставляет функции, с помощью которых вы можете передавать сообщения (messages) между процессами для координации их исполнения. Даже если у вас есть только один компьютер, PVM будет работать на нём, хотя в этом случае у вас не будет "настоящего" параллельного выполнения процессов. Но для учебных целей этого вполне хватит. Далее в этой статье я объясню как по-настоящему распараллеливать процессы с помощью PVM.

Чтобы использовать PVM, вам нужно установить её на вашей линукс-машине. Если вы используете Red Hat Linux, RPM пакет с PVM уже имеется на CD, потому вы можете установить её таким же образом, каким вы устанавливаете все другие пакеты. Будем полагать, что вы установили PVM, создайте следующие директории в вашем домашнем каталоге: ~/pvm3/bin/LINUX/. Зачем? Потому что PVM требует, чтобы некоторые исполняемые файлы, которые вы создадите, были скопированы в эту директорию. Если вы всё сделали - установка завершена. Проверим это, выполнив команду pvm в командной строке. Этим мы запустим консоль PVM (PVM Console), из которой вы можете давать команды PVM и запрашивать информацию по состоянию системы. Если всё установлено правильно, вы увидите приглашение pvm>. Введите команду conf. Вывод от команды должен быть похожим на этот.

pvm> conf
conf
1 host, 1 data format
                    HOST     DTID     ARCH   SPEED       DSIG
               joshicomp    40000    LINUX    1000 0x00408841

Что это означает? Система PVM позволяет вам считать группу соединённых между собой LINUX систем как "виртуальный" компьютер, имеющий намного большую вычислительную мощность, чем индивидуальные машины. Таким образом, PVM будет распределять процессы между несколькими компьютерами. По умолчанию, PVM считает, что только машина, на которой вы работаете должна быть включена в машину PVM. То есть, все процессы, которые вы создаёте, будут запланированы к запуску на одной и той же машине. Команда conf показывает, какие машины (hosts) или узлы (nodes) входят в PVM. В настоящий момент есть только одна машина. Далее, я расскажу, как добавить ещё машин. А теперь выйдите из консоли PVM, дав команду halt

3.1 Демонстрационная Программа

Теперь, когда вы убедились, что система PVM была правильно установлена, давайте посмотрим, как писать программы. Программы для PVM могут писаться на Си и на Фортране. Мы будем использовать язык Си. Чтобы использовать систему PVM вы должны включить в программу несколько вызовов функций PVM. И слинковать (link) библиотеку PVM с вашими программами. Для начала, давайте напишем простую программу, которая будет состоять из двух программ: master и slave. Программа master будет посылать программе slave некоторую строку, программа slave преобразует все символы строки в ПРОПИСНЫЕ (upper case) и перешлёт строку обратно программе master. Ниже приведён код программ master и slave. Чтобы скомпилировать программы дайте такую команду make -f makefile.demo.

[Отсюда вы можете загрузить обе программы в виде tar-файла.]


      1 /* -------------------------------------------------------------------- *
      2  * master_pvm.c                                                         *
      3  *                                                                      *
      4  * Листинг программы master. Простая демонстрация возможностей PVM.         *
      5  * -------------------------------------------------------------------- */
      6 #include <stdio.h>
      7 #include <stdlib.h>
      8 #include <pvm3.h>           /* объявляем константы и функции PVM */
      9 #include <string.h>

     10 int main()
     11 {
     12     int mytid;              /* ID нашей задачи      */
     13     int slave_tid;          /* ID задачи slave */
     14     int result;
     15     char message[] = "hello pvm";
     16
     17     /* регистрируемся в системе PVM и получаем наш ID */
     18     mytid = pvm_mytid();

     19     /* запускаем программу slave */
     20     result = pvm_spawn("slave_pvm", (char**)0, PvmTaskDefault,
     21                         "", 1, &slave_tid);

     22     /* если вызов slave прошёл неуспешно, то ...  */
     23     if(result != 1)
     24     {
     25         fprintf(stderr, "Error: Cannot spawn slave.\n");

     26         /* выходим из PVM */
     27         pvm_exit();
     28         exit(EXIT_FAILURE);
     29     }

     30     /* инициализируем буфер данных для посылки данных программе slave */
     31     pvm_initsend(PvmDataDefault);

     32     /* "пакуем" строку в буфер данных */
     33     pvm_pkstr(message);

     34     /* посылаем строку программе slave с тэгом сообщения (message tag) равным 0 */
     35     pvm_send(slave_tid, 0);

     36     /* ждём и получаем строку-результат от программы slave */
     37     pvm_recv(slave_tid, 0);

     38
     39     /* "распаковываем" результат от программы slave                 */
     40     pvm_upkstr(message);

     41     /* отображаем результат от программы slave */
     42     printf("Data from the slave : %s\n", message);

     43     /* выходим из PVM */
     44     pvm_exit();
     45
     46     exit(EXIT_SUCCESS);
     47 } /* конец main() */

     48 /* конец листинга master_pvm.c */

      1 /* -------------------------------------------------------------------- *
      2  * slave_pvm.c                                                          *
      3  *                                                                      *
      4  * Листинг программы slave. Простая демонстрация возможностей PVM.       *
      5  * -------------------------------------------------------------------- */
      6 #include <stdio.h>
      7 #include <ctype.h>
      8 #include <stdlib.h>
      9 #include <pvm3.h>

     10 #define MSG_LEN     20
     11 void convert_to_upper(char*);

     12 int main()
     13 {
     14     int mytid;
     15     int parent_tid;
     16     char message[MSG_LEN];

     17     /* регистрируемся в системе PVM */
     18     mytid = pvm_mytid();

     19     /* получаем ID задачи master */
     20     parent_tid = pvm_parent();

     21     /* получаем оригинальную строку от master */
     22     pvm_recv(parent_tid, 0);
     23     pvm_upkstr(message);

     24     /* преобразуем  строку в верхний регистр */
     25     convert_to_upper(message);

     26     /* посылаем строку-результат программе master */
     27     pvm_initsend(PvmDataDefault);

     28     pvm_pkstr(message);
     29     pvm_send(parent_tid, 0);

     30     /* выходим из PVM */
     31     pvm_exit();
     32
     33     exit(EXIT_SUCCESS);
     34 } /* конец main() */

     35 /* функция для преобразования данной строки в верхний регистр */
     36 void convert_to_upper(char* str)
     37 {
     38     while(*str != '\0')
     39     {
     40         *str = toupper(*str);
     41         str++;
     42     }
     43 } /* конец convert_to_upper() */

     44 /* конец листинга slave_pvm.c */

      1 # Make-файл для этой демонстрационной программы

      2 .SILENT :
      3 # пути до include-файлов и библиотек PVM
      4 INCDIR=-I/usr/share/pvm3/include
      5 LIBDIR=-L/usr/share/pvm3/lib/LINUX

      6 # линкуем библиотеку PVM
      7 LIBS=-lpvm3
      8 CFLAGS=-Wall
      9 CC=gcc
     10 TARGET=all

     11 # сюда будет помещена наша программа
     12 PVM_HOME=$(HOME)/pvm3/bin/LINUX

     13 all : $(PVM_HOME)/master_pvm $(PVM_HOME)/slave_pvm

     14 $(PVM_HOME)/master_pvm : master_pvm.c
     15     $(CC) -o $(PVM_HOME)/master_pvm master_pvm.c $(CFLAGS) $(LIBS) \
     16           $(INCDIR) $(LIBDIR)

     17 $(PVM_HOME)/slave_pvm : slave_pvm.c
     18     $(CC) -o $(PVM_HOME)/slave_pvm slave_pvm.c $(CFLAGS) $(LIBS) \
     19           $(INCDIR) $(LIBDIR)

Как только программы скомпилировались, вы должны скопировать их в директорию ~/pvm3/bin/LINUX. (makefile делает это по умолчанию). Теперь, чтобы запустить программы, сначала нужно запустить систему PVM. Для этого дайте команду pvm чтобы запустить консоль PVM. В приглашении pvm> введите quit. Должны появиться такие сообщения:

pvm> quit
quit

Console: exit handler called
pvmd still running.
Обратите внимание на последнюю строчку, она говорит о том, что демон PVM (pvmd) всё ещё выполняется. Чтобы запускать программы на PVM, необходимо чтобы демон, управляющий обменом сообщениями, которые мы посылаем в нашей программе, всегда был запущен. Теперь вы сможете запускать программы с помощью таких команд:
[rahul@joshicomp rahul]$ cd ~/pvm3/bin/LINUX/
[rahul@joshicomp LINUX]$ ./master_pvm
Data from the slave : HELLO PVM
[rahul@joshicomp LINUX]$

Обратите внимание, что строка теперь преобразована в верхний регистр, как и ожидалось.

3.2 Разбор программы

В этом разделе мы подробно разберём, как работает эта программа. Прежде всего, чтобы использовать какую-нибудь функцию PVM, нужно включить заголовочный файл pvm3.h в ваши программы. Мы сделали это в 8-ой строке master_pvm.c и в 9-ой строке slave_pvm.c. При компиляции программ, вам нужно слинковать (link) их с библиотекой PVM. Сделать это можно с помощью опции компилятора -lpvm3, это было сделано в строке 7 файла makefile.demo. Также требуется указать компилятору правильные пути к файлам заголовков и библиотек, это было сделано в строках 4 и 5 makefile'а.

Рассмотрим программу master. Сначала мы получаем ID задачи, вызывая функцию PVM pvm_mytid(). Система PVM присваивает каждому процессу уникальное 32-битное целое число, называемое ID задачи, таким же образом Linux присваивает каждому процессу ID процесса. ID задачи помогает идентифицировать процесс, с которым вам нужно установить соединение. Однако, в программе master никогда не используется свой собственный ID задачи (хранящийся в переменной mytid). Здесь мы просто вызваем функцию pvm_mytid(). Эта функция регистрирует процесс в системе PVM и генерирует уникальный ID задачи для процесса. Если мы явно не зарегистрируем наш процесс, PVM автоматически зарегистрирует наш процесс при первом вызове любой функции PVM. Далее мы используем pvm_spawn() чтобы создать процесс slave. Первый параметр, "slave_pvm" - это имя исполняемого файла для slave. Второй параметр - это аргументы, которые вы хотите передать программам slave (это похоже на argv в нормальном Си). Поскольку мы не хотим передавать никаких аргументов, мы устанавливаем это значение в 0. Третий параметр - это флаг, с помощью которого мы можем контролировать, когда и откуда PVM запускает программу slave. Поскольку у нас есть только одна машина, мы устанавливаем это флаг PvmTaskDefault, указывая PVM использовать стандартные критерии при запуске программы slave. Четвёртый параметр - это имя хоста или архитектура ЭВМ, на которой мы будем запускать программу, здесь мы оставим этот параметр пустым, т.к. он используется чтобы указать хост или архитектуру, если флаг установлен в любое другое состояние, кроме PvmTaskDefault. Пятый параметр указывает количество программ slaves для запуска, и шестой параметр - это указатель на массив, в который будут заноситься ID программ slave. Эта функция возвращает количество запущенных программ slave, мы можем использовать эту информацию для проверки.

Сообщение в PVM состоит, в основном, из двух частей, данные и тэг(tag), который идентифицирует тип сообщения. Тэг помогает нам различать разные сообщения. Рассмотрим пример со сложением, который подробно будет далее: предположим, вы ожидаете, что каждая программа slave будет посылать программе master целое число, которое является суммой элементов. Возможна такая ситуация, что в одной из программ slave произойдёт ошибка, и мы захотим переслать программе master целое число, которое идентифицирует код ошибки. Каким же образом программа slave определит, является ли полученное число промежуточным результатом или кодом ошибки? Вот здесь на помощь и приходят тэги. Вы можете присвоить сообщению с промежуточным результатом тэг, скажем, MSG_RESULT, который мы определим с помощью директивы препроцессора #define в каком-нибудь заголовочном файле, и тег, скажем, MSG_ERROR для сообщения указывающего на ошибку. Программа master посмотрит на тэги сообщений и решит, что они содержат: результат или ошибку.

Чтобы послать сообщение, сначала надо "инициализировать" буфер посылки. Это делается вызовом функции pvm_initsend(). Параметр этой функции указывает схему "кодирования", которую нужно будет использовать. Когда мы хотим обменяться данными между машинами с разными архитектурами (например, машина на базе процессора Pentium и рабочая станция SPARC), при посылке нужно закодировать данные, а при получении их обратно раскодировать. Параметр функции pvm_initsend() указывает такую схему кодирования. Значение параметра PvmDataDefault указывает схему кодирования, благодаря которой возможен безопасный обмен данными в среде с гетерогенной архитектурой. Как только буфер был проинициализирован, нам нужно поместить в него данные и закодировать их. В нашем случае в качестве данных выступает строка, поэтому мы используем функцию pvm_pkstr() чтобы её "запаковать", то есть закодировать и поместить данные в буфер. Если бы нам нужно было послать целое число, есть другая функция pvm_pkint(). Также есть подобные функции и для других типов данных. Как только данные запакованы, мы вызываем pvm_send() чтобы послать сообщение. Первый аргумент функции - это ID процесса, которому будет послано сообщение, второй - это тэг сообщения. Поскольку у нас здесь только один тип сообщения, мы устанавливаем значение тэга в 0.

Как только данные посланы, программа slave обработает их и возвратит программе master, как мы увидим. Теперь мы вызываем pvm_recv() чтобы получить данные от программы slave. И снова, параметры - это ID задачи, от которой ожидается получение сообщения, и тэг ожидаемого сообщения. Если желаемое сообщение ещё не было получено, функция ждёт и не возвращает управление программе. Таким образом, теперь master ждёт, когда slave обработает данные. Когда сообщение приходит, данные всё ещё находятся в буфере получения. Сначала нужно их "распаковать", то есть получить оригинальное сообщение. Это декодирование производится с помощью функции pvm_upkstr(). Потом мы отображает обработанную строку.

Перед выходом, программа должна сообщить системе PVM, что она завершается, и что можно освободить все занимаемые ею системные ресурсы. Это делается при помощи вызова функции pvm_exit(). После этого программа master завершается.

В программе slave всё должно быть понятно. Сначала она находит ID задачи master'а ( который является родительским процессом для программы slave, т.к. он её и запустил), вызывая функцию pvm_parent(). Потом slave получает сообщение со строкой от master'а, преобразует её в верхний регистр и посылает результирующую строку обратно.

3.3 Программа сложения

Теперь, когда вы знаете основы написания программ для PVM, воплотим в жизнь алгоритм сложения, который мы разработали ранее. У нас будет один master и четыре программы slave. Сначала master запустит четыре salve'а и предаст каждому свою часть данных. Программы slave сложат данные и отправят результаты master'у. Для этого у нас есть два типа сообщений: первый - когда master посылает данные slave, для которого мы будем использовать тэг MSG_DATA, и другой - когда salve посылает результаты master'у, для такого сообщения мы будем использовать тэг MSG_RESULT. Остальное просто. Листинг программ master и slave дан ниже.


      1 /* -------------------------------------------------------------------- *
      2  * common.h                                                             *
      3  *                                                                      *
      4  * Этот заголовочный файл определяет некоторые общие константы.                      *
      5  * -------------------------------------------------------------------- */
      6 #ifndef COMMON_H
      7 #define COMMON_H

      8 #define NUM_SLAVES      4                   /* количество slave     */
      9 #define SIZE            100                 /* полный размер данных */
     10 #define DATA_SIZE       (SIZE/NUM_SLAVES)   /* размер данных для slave*/

     11 #endif
     12 /* конец common.h */

      1 /* -------------------------------------------------------------------- *
      2  * tags.h                                                               *
      3  *                                                                      *
      4  * Этот заголовочный файл определяет тэги для сообщений.    *
      5  * -------------------------------------------------------------------- */
      6 #ifndef TAGS_H
      7 #define TAGS_H

      8 #define MSG_DATA            101     /* данные от master к slave  */
      9 #define MSG_RESULT          102     /* данные от slave к master  */

     10 #endif

     11 /* конец tags.h */

  1 /* -------------------------------------------------------------------- *
  2  * master_add.c                                                         *
  3  *                                                                      *
  4  * Программа master для сложения элементов массива с использованием PVM *
  5  * -------------------------------------------------------------------- */
  6 #include <stdio.h>
  7 #include <stdlib.h>
  8 #include <pvm3.h>           /* константы и объявления функций PVM*/
  9 #include "tags.h"           /* тэг для сообщений */
 10 #include "common.h"         /* общие константы   */

 11 int get_slave_no(int*, int);

 12 int main()
 13 {
 14     int mytid;
 15     int slaves[NUM_SLAVES]; /* массив для хранения ID задач slave    */
 16     int items[SIZE];        /* данные для обработки                  */
 17     int result, i, sum;
 18     int results[NUM_SLAVES];    /* результаты от программ slave */

 19     /* регистрируемся в системе PVM  */
 20     mytid = pvm_mytid();

 21     /* инициализируем массив `items' */
 22     for(i = 0; i < SIZE; i++)
 23         items[i] = i;

 24     /* запускаем программы slave     */
 25     result = pvm_spawn("slave_add", (char**)0, PvmTaskDefault,
 26                        "", NUM_SLAVES, slaves);

 27     /* проверяем количество реально запущенных программ slave */
 28     if(result != NUM_SLAVES)
 29     {
 30         fprintf(stderr, "Error: Cannot spawn slaves.\n");
 31         pvm_exit();
 32         exit(EXIT_FAILURE);
 33     }

 34     /* распределяем данные среди программ slave     */
 35     for(i = 0; i < NUM_SLAVES; i++)
 36     {
 37         pvm_initsend(PvmDataDefault);
 38         pvm_pkint(items + i*DATA_SIZE, DATA_SIZE, 1);
 39         pvm_send(slaves[i], MSG_DATA);
 40     }

 41     /* получаем результат от программ slave      */
 42     for(i = 0; i < NUM_SLAVES; i++)
 43     {
 44         int bufid, bytes, type, source;
 45         int slave_no;
 46
 47         /* получаем сообщение от любой из программ slave  */
 48         bufid = pvm_recv(-1, MSG_RESULT);

 49         /* получаем информацию о сообщении */
 50         pvm_bufinfo(bufid, &bytes, &type, &source);
 51
 52         /* получаем номер slave, которая послала данное сообщение  */
 53         slave_no = get_slave_no(slaves, source);

 54         /* распаковываем результат в правильную позицию */
 55         pvm_upkint(results + slave_no, 1, 1);
 56     }

 57     /* находим окончательный результат  */
 58     sum = 0;
 59     for(i = 0; i < NUM_SLAVES; i++)
 60         sum += results[i];

 61     printf("The sum is %d\n", sum);

 62     /* выходим из PVM */
 63     pvm_exit();

 64     exit(EXIT_SUCCESS);
 65 } /* конец main() */
 66
 67 /* функция возвращает номер slave'а по его ID задачи */
 68 int get_slave_no(int* slaves, int task_id)
 69 {
 70     int i;

 71     for(i = 0; i < NUM_SLAVES; i++)
 72         if(slaves[i] == task_id)
 73             return i;

 74     return -1;
 75 } /* конец get_slave_no() */

 76 /* конец master_add.c */


  1 /* -------------------------------------------------------------------- *
  2  * slave_add.c                                                          *
  3  *                                                                      *
  4  * Программа slave для сложения элементов массива с использованием PVM  *
  5  * -------------------------------------------------------------------- */
  6 #include <stdlib.h>
  7 #include <pvm3.h>
  8 #include "tags.h"
  9 #include "common.h"

 10 int main()
 11 {
 12     int mytid, parent_tid;
 13     int items[DATA_SIZE];           /* данные, посылаемые master  */
 14     int sum, i;
 15
 16     /* регистрируемся в системе PVM  */
 17     mytid = pvm_mytid();

 18     /* получаем ID задачи для master */
 19     parent_tid = pvm_parent();

 20     /* получаем данные от master */
 21     pvm_recv(parent_tid, MSG_DATA);
 22     pvm_upkint(items, DATA_SIZE, 1);

 23     /* находим сумму элементов */
 24     sum = 0;
 25     for(i = 0; i < DATA_SIZE; i++)
 26         sum = sum + items[i];

 27     /* посылаем результат  master */
 28     pvm_initsend(PvmDataDefault);
 29     pvm_pkint(&sum, 1, 1);
 30     pvm_send(parent_tid, MSG_RESULT);

 31     /* выходим из PVM  */
 32     pvm_exit();
 33
 34     exit(EXIT_SUCCESS);
 35 } /* конец main() */


  1 # Make file для программы сложения элементов массива с использованием PVM - makefile.add

  2 .SILENT :
  3 # пути для include файлов и библиотек PVM
  4 INCDIR=-I/usr/share/pvm3/include
  5 LIBDIR=-L/usr/share/pvm3/lib/LINUX

  6 # линкуем (link) библиотеку PVM
  7 LIBS=-lpvm3
  8 CFLAGS=-Wall
  9 CC=gcc
 10 TARGET=all

 11 # в эту директорию будут помещаться исполняемые файлы
 12 PVM_HOME=$(HOME)/pvm3/bin/LINUX

 13 all : $(PVM_HOME)/master_add $(PVM_HOME)/slave_add

 14 $(PVM_HOME)/master_add : master_add.c common.h tags.h
 15     $(CC) -o $(PVM_HOME)/master_add master_add.c $(CFLAGS) $(LIBS) \
 16           $(INCDIR) $(LIBDIR)
 17
 18 $(PVM_HOME)/slave_add : slave_add.c common.h tags.h
 19     $(CC) -o $(PVM_HOME)/slave_add slave_add.c $(CFLAGS) $(LIBS) \
 20          $(INCDIR) $(LIBDIR)

Сначала давайте рассмотрим программу slave, т.к. она проще. Slave получает 25 элементов массива от master и помещает их в массив items, находит их сумму и посылает результат программе master с тэгом сообщения MSG_RESULT. Теперь перейдём к программе master. Мы определяем массив slaves размера NUM_SLAVES, который будет хранить ID задач slave, запущенных родительской программой. Есть ещё один массив results, в котором будут храниться результаты, полученные от программ slave. Сначала master инициализирует массив items и запускает программы slave. После этого он распределяет данные среди них. В вызове функции pvm_pkint() (строке 38) первый параметр - указатель на массив, в котором хранятся целые числа, второй - количество чисел для упаковки, и третий - "расстояние". Расстояние - это количество элементов, которое нужно пропустить при упаковке. Когда оно равно 1, последовательно пакуются все элементы. Когда оно равно 2, PVM будет пропускать 2 элемента при упаковке, в результате все элементы с чётными номерами (0, 2, 4 ...) будут упакованы. Здесь мы установим его значение в 1.

Как только данные были распределены между программами slave, master должен ждать пока программы slave вернут промежуточные результаты. В одном случае master сначала получит результат от программы slave 0 (т.е. slave чей ID задачи хранится в slave[0]), потом от slave 1 и так далее. Но это, возможно, не самый эффективный подход. Например, если slave 0 на более медленной машине, чем slave 1, 2 и 3. В этом случае, пока мастер ждёт результата от slave 0, результаты от slaves 1, 2 и 3 уже могут быть собраны, т.к. подсчёт уже завершён. В нашем примере всё будет работать хорошо, но представьте ситуацию, в которой slave по завершении одной партии данных подаётся другая. Поэтому, master всегда должен быть готов принять результат от любой из программ slave. Что и было сделано здесь.

В вызове функции pvm_recv() (строка 48), мы знаем, что первый параметр - ID задачи от которой ожидается сообщение. Установим значение этого параметра в -1, это указывает, что нужно принимать сообщения от любой задачи с тэгом сообщения MSG_RESULT. Полученное сообщение вместе с некоторой контрольной информацией помещается в активный буфер приёма . Вызов возвращает уникальный ID задачи для этого буфера. Теперь, мы хотим узнать, от кого пришло сообщение, для того чтобы присвоить данные из сообщения правильным элементам массива results. Функция pvm_bufinfo() возвращает информацию о сообщении, которое находится в буфере, информация включает в себя тэг сообщения, количество байтов, и ID задачи, от которой получено сообщение. Как только у нас есть этот ID, мы устанавливаем значение правильного элемента массива results в значение, посланное программой slave. Остальное в программе должно быть понятно.

3.4 Работаем с PVM

Если вы заинтересовались, можете подумать о некоторых задачах, для которых вы можете написать параллельные программы. Конечно, ошибки неизбежны. В консоли PVM можно использовать команду halt, которая убивает демона PVM. После этого все процессы PVM остановятся или вы можете завершить их вручную командой Linux kill. В случае, если у вас есть сеть из Linux машин, соединённых, скажем, локальной сетью (LAN), можно делать "настоящий" параллельный процессинг. Прежде всего, установите PVM на всех машинах, которые вы хотите использовать, дайте команду add в консоли PVM чтобы добавить хосты к виртуальной машине. PVM назначит для исполнения некоторые из процессов на этих хостах, таким образом достигается настоящий параллельный процессинг.

4. Реализация на базе MPI

В предыдущем разделе мы увидели реализацию программы сложения с использованием PVM. Теперь давайте рассмотрим другой подход, который может быть использован при разработке параллельных программ. Этот подход - использование библиотеки MPI. MPI означает Интерфейс передачи сообщений (Message Passing Interface). Это стандарт, разработанный чтобы позволить нам писать переносимые приложения с передачей сообщений. Он предоставляет функции для обмена сообщениями и многие другие функции. Обратите внимание, что если PVM - программная система, то MPI - стандарт, поэтому могут существовать многие его реализации. Мы будем использовать реализацию под названием LAM, что означает Локальный Мультикомпьютер (Local Area Multicomputer). Эта реализация так же доступна на CD с Red Hat Linux как RPM пакет, поэтому проблем с установкой быть не должно.

После того, как вы установили RPM пакет, перейдите в директорию /usr/boot и создайте файл с именем conf.lam, в нём наберите в одну строку следующее: lamd $inet_topo. В этой же директории находится файл bhost.def, если это не так, создайте его и впишите одной строчкой следующее: localhost. Теперь, чтобы проверить, что всё работает правильно, наберите в командной строке lamboot. Вы получите такой ответ:

[rahul@joshicomp boot]$ lamboot

LAM 6.3.1/MPI 2 C++/ROMIO - University of Notre Dame

[rahul@joshicomp boot]$

Если вывод команды содержит ошибку, вероятно, есть проблемы с инсталляцией, попробуйте повторить шаги выше, или посмотрите man страницу lamboot(1) для устранения неполадок.

Полагая, что LAM/MPI правильно установлен в вашей системе, давайте снова напишем маленькую демонстрационную программу для MPI.

4.1 Демонстрационная MPI программа

Мы снова напишем простую master - slave программу, в которой мы будем вычислять значение выражения (a + b) * (c - d). Программа master получит значения a, b, c, и d от пользователя и передаст их программам slave: одна программа slave будет вычислять выражение (a + b), другая - выражение (c - d). Потом master вычислит окончательный результат. Листинг программы приводится ниже.


  1 /* -------------------------------------------------------------------- *
  2  * mpi_demo.c                                                           *
  3  *                                                                      *
  4  * Простая MPI программа для вычисления значения выражения.             *
  5  * -------------------------------------------------------------------- */
  6 #include <stdio.h>
  7 #include <stdlib.h>
  8 #include <lam/mpi.h>            /* для констант и функций MPI */

  9 #define MSG_DATA        100     /* сообщение от программы master к программам slave */
 10 #define MSG_RESULT      101     /* сообщение от программы slave к программе master */

 11 #define MASTER          0       /* ранг master'а  */
 12 #define SLAVE_1         1       /* ранг первой программы slave          */
 13 #define SLAVE_2         2       /* ранг второй программы slave          */

 14 /* функции для выполнения задачи master и двух программ slaves          */
 15 void master(void);
 16 void slave_1(void);
 17 void slave_2(void);

 18 int main(int argc, char** argv)
 19 {
 20     int myrank, size;
 21
 22     /* инициализируем систему MPI */
 23     MPI_Init(&argc, &argv);

 24     /* получаем размер коммуникатора, т.е. число процессов  */
 25     MPI_Comm_size(MPI_COMM_WORLD, &size);

 26     /* проверяем число запущенных процессов  */
 27     if(size != 3)
 28     {
 29         fprintf(stderr, "Error: Three copies of the program should be run.\n");
 30         MPI_Finalize();
 31         exit(EXIT_FAILURE);
 32     }
 33
 34     /* получаем ранг процесса */
 35     MPI_Comm_rank(MPI_COMM_WORLD, &myrank);

 36     /* выполняем задачи в соответствии с рангом */
 37     if(myrank == MASTER)
 38         master();
 39     else if(myrank == SLAVE_1)
 40         slave_1();
 41     else
 42         slave_2();

 43     /* выходим из системы MPI */
 44     MPI_Finalize();

 45     exit(EXIT_SUCCESS);
 46 } /* конец main() */

 47 /* функция для выполнения задачи программы master */
 48 void master(void)
 49 {
 50     int a, b, c, d;
 51     int buf[2];
 52     int result1, result2;
 53     MPI_Status status;

 54     printf("Enter the values of a, b, c, and d: ");
 55     scanf("%d %d %d %d", &a, &b, &c, &d);

 56     /* посылаем a и b первой программе slave */
 57     buf[0] = a;
 58     buf[1] = b;
 59     MPI_Send(buf, 2, MPI_INT, SLAVE_1, MSG_DATA, MPI_COMM_WORLD);

 60     /* посылаем c и d второй программе slave */
 61     buf[0] = c;
 62     buf[1] = d;
 63     MPI_Send(buf, 2, MPI_INT, SLAVE_2, MSG_DATA, MPI_COMM_WORLD);

 64     /* получаем результаты от программ slave */
 65     MPI_Recv(&result1, 1, MPI_INT, SLAVE_1, MSG_RESULT,
 66              MPI_COMM_WORLD, &status);
 67     MPI_Recv(&result2, 1, MPI_INT, SLAVE_2, MSG_RESULT,
 68              MPI_COMM_WORLD, &status);

 69     /* окончательный результат */
 70     printf("Value of (a + b) * (c - d) is %d\n", result1 * result2);
 71 } /* конец master() */

 72 /* функция для выполнения задачи первой программы slave */
 73 void slave_1(void)
 74 {
 75     int buf[2];
 76     int result;
 77     MPI_Status status;
 78
 79     /* получаем два числа от master */
 80     MPI_Recv(buf, 2, MPI_INT, MASTER, MSG_DATA, MPI_COMM_WORLD, &status);
 81
 82     /* находим a + b  */
 83     result = buf[0] + buf[1];

 84     /* посылаем результат программе master  */
 85     MPI_Send(&result, 1, MPI_INT, MASTER, MSG_RESULT, MPI_COMM_WORLD);
 86 } /* конец slave_1() */

 87 /* функция для выполнения задачи второй программы slave  */
 88 void slave_2(void)
 89 {
 90     int buf[2];
 91     int result;
 92     MPI_Status status;
 93
 94     /* получаем два числа от master */
 95     MPI_Recv(buf, 2, MPI_INT, MASTER, MSG_DATA, MPI_COMM_WORLD, &status);
 96
 97     /* находим c - d  */
 98     result = buf[0] - buf[1];

 99     /* посылаем результат программе master */
100     MPI_Send(&result, 1, MPI_INT, MASTER, MSG_RESULT, MPI_COMM_WORLD);
101 } /* конец slave_2() */

102 /* конец mpi_demo.c */

  1 # Make файл для нашей демонстрационной программы - makefile.mpidemo
  2 .SILENT:
  3 CFLAGS=-I/usr/include/lam -L/usr/lib/lam
  4 CC=mpicc

  5 mpi_demo : mpi_demo.c
  6     $(CC) $(CFLAGS) mpi_demo.c -o mpi_demo

Чтобы скомпилировать эту программу дайте команду make -f makefile.mpidemo. Как только вы скомпилировали программу, чтобы её запустить, нужно сначала загрузить нашу локальную мультикомпьютерную систему (Local Area Multicomputer system). Это делается с помощью команды lamboot. После этого вы можете запустить программу с помощью такой команды: mpirun -np 3 mpi_demo.

[rahul@joshicomp parallel]$ lamboot

LAM 6.3.1/MPI 2 C++/ROMIO - University of Notre Dame

[rahul@joshicomp parallel]$ mpirun -np 3 mpi_demo
Enter the values of a, b, c, and d: 1 2 3 4
Value of (a + b) * (c - d) is -3
[rahul@joshicomp parallel]$

4.2 Разбор программы

Чтобы использовать систему MPI и её функции, нужно включить в программу заголовочный файл mpi.h, это было сделано в строке 8. В случае с PVM, процессы распознаются по их ID задачи. В случае с MPI, система присваивает каждому процессу уникальное целое число, называемое его рангом (rank), нумерация начинается с 0. Ранг используется для идентификации процесса и обменом информации с ним. Каждый процесс является членом некоторого коммуникатора (communicator). Коммуникатор можно воспринимать как группу процессов, которые могут обмениваться информацией друг с другом. По умолчанию, каждый процесс - член коммуникатора под названием MPI_COMM_WORLD. Хотя мы можем создавать другие коммуникаторы, этого деалть не рекомендуется, т.к. это ведёт к ненужному увеличению сложности, поэтому мы будем использовать коммуникатор MPI_COMM_WORLD.

Любая MPI программа должна сначала вызвать функцию MPI_Init(). Эта функция используется процессом чтобы войти в систему MPI и чтобы производить другие специфичные инициализации, требуемые системой. На следующем шагу мы получаем размер коммуникатора MPI_COMM_WORLD, т.е. количество процессов в нём, используя функцию MPI_Comm_size(). Первый параметр - коммуникатор, второй - указатель на целое число, в которое будет помещён размер. Здесь нам нужно ровно 3 процесса, один master и два slave. После этого мы получаем ранг, вызывая функцию MPI_Comm_rank(). Три процесса будут иметь ранги 0, 1 и 2. Все эти процессы в основном идентичны, т.е. между ними нет наследуемого отношения хозяин-раб. Поэтому мы сами решаем, кто будет master, а кто будет slave. Мы выбираем для master ранг 0, а ранги 1 и 2 для slave. Вы так же должны были заметить, что мы включили код и для программы master, и для программ slave в одну программу. На основании ранга наша программа начинает выполнять нужную функцию. Заметьте, что здесь нет порождения процессов, как в PVM, и, как мы увидим, указать количество процессов для запуска можно из командной строки. Как только выполнение программы завершено, мы должны вызвать функцию MPI_Finalize() чтобы выйти из системы.

Давайте рассмотрим функцию master. После чтения значений a, b, c и d от пользователя, master должен послать a и b slave 1, c и d slave 2. Вместо того, чтобы посылать переменные по отдельности, мы упакуем их в массив и пошлём его. Всегда лучше упаковать все данные, которые вы хотите послать в одно сообщение, чем посылать несколько с индивидуальными данными. Это сокращает временные расходы на передачу сообщений. Как только буфер готов, в отличие от PVM нам не нужно паковать или кодировать данные, MPI будет управлять кодированием изнутри. Поэтому мы можем непосредственно обратиться к функции MPI_Send() чтобы послать данные. Первый параметр (строка 59) - это адрес буфера, второй - количество элементов в сообщении, третий - спецификация типа данных буфера, здесь она установлена в MPI_INT, указывая на то, что буфер - это массив из целых чисел. Далее идёт ранг процесса, которому мы хотим послать сообщение. Здесь он равен SLAVE_1 (определён с помощью директивы препроцессора #define как 1). Далее следует тэг сообщения, похожий на тот, что используется в PVM. Последний параметр - это коммуникатор, членом которого является получатель сообщения, в нашем случае этот параметр равен MPI_COMM_WORLD.

Как только данные были распределены между программами slave, master должен ждать от них возвращения результата. Для простоты давайте сначала получим сообщение от slave 1, а потом от slave 2. Чтобы получить сообщение, мы используем функцию MPI_Recv(). Снова, упаковка и декодирование выполняется MPI изнутри. Первый аргумент (строка 65) - это адрес буфера, в который будут получаться данные. Второй - размер буфера в терминах количества элементов, который в нашем случае 1. Следующий параметр - тип данных, который здесь MPI_INT. Следующие три параметра ранг источника сообщения, тэг ожидаемого сообщения и коммуникатор, членом которого является посылатель сообщения. Последний аргумент - это указатель на структуру типа MPI_Status, в которую будет помещаться некоторая информация о состоянии (хотя мы игнорируем эту информацию). Теперь вы знаете основные термины MPI. Функции slave_1() и slave_2() должны быть понятны без объяснений.

В этой программе код master, вместе с кодами программ slave был в одном файле. Далее мы увидим, как можно использовать несколько исполняемых файлов. Из makefile'а мы видим что, для того чтобы скомпилировать программу на MPI, есть программа mpicc которая линкует необходимые библиотеки автоматически. Чтобы запустить программу используйте команду mpirun -np 3 mpi_demo после загрузки LAM. Здесь мы приказываем LAM создать 3 процесса, один master и два slave.

4.3 И снова программа сложения

Теперь давайте перепишем программу сложения, которую мы разработали до использования MPI. Здесь мы также рассмотрим как запускать отдельные программы в MPI. Когда мы используем единственную исполняемую программу на MPI, мы называем её приложением типа Одна Программа Много Данных (Single Program Multiple Data или просто SPMD). Когда есть две и более программ, мы называем такое приложение Много Программ Много Данных (Multiple Program Multiple Data или MPMD). В LAM MPMD программы выполняются с помощью схемы приложения (application schema). Но, прежде, давайте посмотрим на исходники программ master и slave.


  1 /* -------------------------------------------------------------------- *
  2  * master_mpi.c                                                         *
  3  *                                                                      *
  4  * Программа master для сложения элементов массива с использованием MPI *
  5  * -------------------------------------------------------------------- */
  6 #include <stdio.h>
  7 #include <stdlib.h>
  8 #include <lam/mpi.h>        /* для констант и функций MPI */
  9 #include "tags.h"           /* тэги для разных сообщений  */
 10 #include "common.h"         /* общие константы  */

 11 int main(int argc, char** argv)
 12 {
 13     int size, i, sum;
 14     int items[SIZE];
 15     int results[NUM_SLAVES];
 16     MPI_Status status;

 17     /* инициализируем систему MPI */
 18     MPI_Init(&argc, &argv);

 19     /* проверяем количество запущенных процессов */
 20     MPI_Comm_size(MPI_COMM_WORLD, &size);

 21     if(size != 5)
 22     {
 23         fprintf(stderr, "Error: Need exactly five processes.\n");
 24         MPI_Finalize();
 25         exit(EXIT_FAILURE);
 26     }

 27     /* инициализируем массив `items'  */
 28     for(i = 0; i < SIZE; i++)
 29         items[i] = i;

 30     /* распределяем данные между программами slave */
 31     for(i = 0; i < NUM_SLAVES; i++)
 32         MPI_Send(items + i*DATA_SIZE, DATA_SIZE, MPI_INT, i + 1,
 33                  MSG_DATA, MPI_COMM_WORLD);

 34     /* собираем результаты от программ slave */
 35     for(i = 0; i < NUM_SLAVES; i++)
 36     {
 37         int result;
 38
 39         MPI_Recv(&result, 1, MPI_INT, MPI_ANY_SOURCE, MSG_RESULT,
 40                  MPI_COMM_WORLD, &status);
 41         results[status.MPI_SOURCE - 1] = result;
 42     }

 43     /* находим окончательный результат */
 44     sum = 0;
 45     for(i = 0; i < NUM_SLAVES; i++)
 46         sum = sum + results[i];

 47     printf("The sum is %d\n", sum);

 48     /* выходим из системы MPI */
 49     MPI_Finalize();

 50     exit(EXIT_SUCCESS);
 51 } /* конец main() */

 52 /* конец master_mpi.c */

  1 /* -------------------------------------------------------------------- *
  2  * slave_mpi.c                                                          *
  3  *                                                                      *
  4  * Программа slave для сложения элементов массива с использованием MPI. *
  5  * -------------------------------------------------------------------- */
  6 #include <stdio.h>
  7 #include <stdlib.h>
  8 #include <lam/mpi.h>        /* для констант и функций MPI */
  9 #include "tags.h"           /* тэги сообщений */
 10 #include "common.h"         /* общие константы */

 11 #define MASTER  0           /* ранг программы master */

 12 int main(int argc, char** argv)
 13 {
 14     int items[DATA_SIZE];
 15     int size, sum, i;
 16     MPI_Status status;

 17     /* инициализируем систему MPI */
 18     MPI_Init(&argc, &argv);

 19     /* проверяем количество запущенных процессов */
 20     MPI_Comm_size(MPI_COMM_WORLD, &size);

 21     if(size != 5)
 22     {
 23         fprintf(stderr, "Error: Need exactly five processes.\n");
 24         MPI_Finalize();
 25         exit(EXIT_FAILURE);
 26     }

 27     /* получаем данные от master */
 28     MPI_Recv(items, DATA_SIZE, MPI_INT, MASTER, MSG_DATA,
 29              MPI_COMM_WORLD, &status);

 30     /* находим сумму */
 31     sum = 0;
 32     for(i = 0; i < DATA_SIZE; i++)
 33         sum = sum + items[i];

 34     /* посылаем результат master */
 35     MPI_Send(&sum, 1, MPI_INT, MASTER, MSG_RESULT, MPI_COMM_WORLD);

 36     /* выходим из системы MPI */
 37     MPI_Finalize();

 38     exit(EXIT_SUCCESS);
 39 } /* конец main() */

 40 /* конец slave_mpi.c */

  1 # Make file для программы сложения элементов массива с использованием MPI - makefile.mpiadd
  2 .SILENT:
  3 CFLAGS=-I/usr/include/lam  -L/usr/lib/lam
  4 CC=mpicc

  5 all : master_mpi slave_mpi

  6 master_mpi : master_mpi.c common.h tags.h
  7     $(CC) $(CFLAGS) master_mpi.c -o master_mpi

  8 slave_mpi : slave_mpi.c common.h tags.h
  9     $(CC) $(CFLAGS) slave_mpi.c -o slave_mpi

Чтобы скомпилировать программы наберите make -f makefile.mpiadd. (Файлы common.h и tags.h используются так же, как и в программе на PVM.) Будут созданы 2 файла: master_mpi и slave_mpi. Как приказать MPI запустить обе программы? Для этого применяется файл схемы приложения (application schema file). Этот файл указывает программы, которые нужно запустить, узлы, на которых запустить и количество копий для запуска. Создайте новый файл add.schema и вставьте туда следующее:

# Схема приложения для программы сложения с использованием MPI
n0 master_mpi
n0 -np 4 slave_mpi

Этот файл указывает, что MPI должен запустить 1 копию master (с рангом 0) и 4 копии slave на узле n0, т.е. локальном узле. Вы можете указать больше параметров в этой схеме, например, аргументы командной строки и т.д. За подробностями обращайтесь к man странице appschema(1). Как только ваш файл схемы приложения готов, вы можете запустить программу так:

[rahul@joshicomp parallel]$ lamboot

LAM 6.3.1/MPI 2 C++/ROMIO - University of Notre Dame

[rahul@joshicomp parallel]$ mpirun add.schema
The sum is 4950
[rahul@joshicomp parallel]$

Большая часть программы должна быть понятна. В строке 39, когда получаем промежуточные результаты от slave, мы указываем источник сообщений как MPI_ANY_SOURCE, т.к. мы хотим отвечать любой программе slave, как только она получит результат, мы уже говорили об этом ранее. В этом случае структура status содержит актуального посылателя сообщения в поле MPI_SOURCE. Мы используем это, чтобы записать промежуточный результат в правильное место массива results.

Если у вас есть сеть, соединённых между собой компьютеров, вы можете заставить программы запускаться на многих компьютерах, просто модифицируя файл схемы приложения. Вместо указания узла n0 в качестве хоста, укажите имя машины и количество процессов, которое вы хотите на ней запустить. За более подробной информацией по этой теме обращайтесь к man страницам и ссылкам в конце статьи.

5. Заключение

Мы увидели как писать параллельные программы, используя библиотеки PVM и MPI. Т.к. эти библиотеки доступны на большом числе платформ и являются стандартом де-факто построения параллельных программ, если нужно программы, написанные с использованием PVM или MPI, будут работать с минимальными модификациями на огромном числе машин. В этой статье мы уделили особое внимание функциям обмена информацией типа точка - точка (point to point), которые предоставляют эти библиотеки, и их использованию в передаче сообщений. Кроме того, и PVM и MPI предоставляют некоторое количество расширенных функций, таких, как коллективная передача данных (broadcasting или multicasting), группы процессов и управления, редукционные функции и т.д. Я столько приветствую использование вами этих расширенных функций. Это свободное программное обеспечение позволяет вам использовать сеть соединённых между собой компьютеров, как один большой компьютер, поэтому если у вас есть какая-нибудь задача, требующая огромных вычислительных затрат, вы можете использовать компьютерную сеть вашего университета или офиса. Возможно, вам пригодятся эти книги, в них подробно написано, как всё это можно осуществить. А так же доступно огромное количество книг и обучалок чтобы помочь вам. Ниже я привожу список материалов, которыми пользовался сам.

  1. PVM: Parallel Virtual Machine - A User's Guide and Tutorial for Networked Parallel Computing, Al Geist, Adam Beguelin, Jack Dongarra, Robert Manchek, Weicheng Jiang and Vaidy Sunderam, MIT Press. Можно достать на www.netlib.org
  2. MPI: The Complete Reference, Marc Snir, Steve Otto, Steven Huss-Lederman, David Waker and Jack Dongarra, MIT Press. Можно достать на www.netlib.org.
  3. RS/6000 SP: Practical MPI Programming,Yukiya Aoyama and Jan Nakano, International Techical Support Organization, IBM Corporation, www.redbooks.ibm.com.
  4. A Beginner's Guide to PVM Parallel Virtual Machine, Clay Breshears and Asim YarKhan, Joint Institute of Computational Science, University of Tennessee, USA. www-jics.cs.utk.edu/PVM/pvm/_guide.html.
  5. PVM: An Introduction to Parallel Virtual Machine, Emily Angerer Crawford, Office of Information Technology, High Performance Computing, www.hpc.gatech.edu/seminar/pvm.html.

6. Благодарности

Я хочу поблагодарить руководителя моего проекта доктора Uday Khedker за его поддержку и помощь. Я хочу поблагодарить Center for Developement of Advanced Computing за предоставленную возможность запускать MPI и PVM программы на суперкомпьютере PARAM и лично доктора Anabarsu за помощь при внедрении программ в жизнь.

 


Copyright © 2001, Rahul U. Joshi.
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 65 of Linux Gazette, April 2001

Вернуться на главную страницу