Отправляет email-рассылки с помощью сервиса Sendsay

Linux Gazette на русском

  Все выпуски  

Linux Gazette на русском - Параллельный процессинг - I


Служба Рассылок Subscribe.Ru проекта Citycat.Ru

Здравствуйте,

Перед вами первый выпуск рассылки, в которой вы всегда найдете новые переводы и статьи с сайта русской версии Linux Gazette (http://gazette.linux.ru.net), а также познакомитесь с новостями проекта.

В этом выпуске вам предлагается первые три части монументальной статьи о параллельном процессинге, работу над которой недавно завершил член нашей команды переводчиков, Роман Шумихин.

 


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


 

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

 


Оригинал статьи: http://www.linuxgazette/issue65/joshi.html

Перевод также доступен на: http://gazette.linux.ru.net/lg65/articles/rus-joshi.html

Первоначально опубликовано в апрельском выпуске Linux Gazette.


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

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

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

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


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

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

Четыре программы, скажем, P0, P1, P2 и P3 будут решать задачу. P0 найдёт сумму элементов массива от items[0] до items[24]. P1 таким же образом найдёт сумму от items[25] до items[49], P2 от items[50] до items[74] и P3 от items[75] до items[99].

После выполнения этих программ должна быть запущена другая программа, которая найдёт сумму 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.


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

     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 
      7 #include 
      8 #include 
      9 #include 

     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 
  7 #include 
  8 #include            /* константы и объявления функций 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 
  7 #include 
  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. Остальное в программе должно быть понятно.

 

 


{...окончание статьи в следующем выпуске...}

http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться Рейтингуется SpyLog

В избранное