Параллельный Процессинг в ОС Linux с использованием систем PVM и MPI
Автор: © Rahul U. Joshi
|
Цель этой статьи - вкратце рассказать читателю о PVM и MPI, двух широко используемых программных системах для разработки программ параллельного обмена сообщениями. Они позволяют нам использовать группу гетерогенных компьютеров на базе ОС UNIX/LINUX, объединённых сетью, как отдельную машину для решения большой задачи. 1. Введение в параллельный процессингПараллельный процессинг - это форма компьютинга, в которой некоторое число процессов выполняются одновременно, так что время, требуемое для решения задачи, сокращается. Раньше параллельный процессинг использовался для крупномасштабных симуляций (например, молекулярных симуляций, симуляции взрыва атомной бомбы и т.д.), решения числовых задач большого объёма и задач обработки данных (например, обработка результатов переписи населения). Как бы то ни было, с быстрым падением цен на компьютерное "железо" параллельный процессинг всё чаще используется для решения более рутинных задач. Многопроцессорные серверы существуют уже достаточно давно. Кстати, параллельный процессинг тоже используется и в вашем компьютере. Например, графический процессор работает совместно с основным процессором, чтобы отображать графику на вашем мониторе, это тоже форма параллельного процессинга.
Но, кроме аппаратных средств, для параллельного процессинга требуется ещё и программная поддержка, которая даст нам возможность параллельно запускать программы и координировать их исполнение. Такая координация необходима из-за зависимостей параллельных программ друг от друга. Вы поймёте, что к чему, когда мы доберёмся до примера. Самый широко используемый метод добиться такой координации - это обмен сообщениями (message passing) , при котором программы координируют своё выполнение и общаются между собой, посылая сообщения одна другой. Так, например, программа может сказать другой программе "Ok! Вот промежуточный результат, который нужен тебе для продолжения твоего выполнения". Если всё это звучит слишком абстрактно, давайте разберём очень простой пример. 2. Очень простая задачаВ этом разделе мы рассмотрим очень простую задачу и то, как можно использовать параллельный
процессинг, чтобы ускорить процесс нахождения её решения. Задача состоит в нахождении суммы
некоторого количества целых чисел, хранящихся в массиве. Предположим, в массиве
Помня о сказанном выше, давайте напишем алгоритм для программы 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, создайте следующие директории в вашем домашнем
каталоге: pvm> conf conf 1 host, 1 data format HOST DTID ARCH SPEED DSIG joshicomp 40000 LINUX 1000 0x00408841 Что это означает? Система PVM позволяет вам считать группу соединённых между собой
LINUX систем как "виртуальный" компьютер, имеющий намного большую вычислительную мощность,
чем индивидуальные машины. Таким образом, PVM будет распределять процессы
между несколькими компьютерами. По умолчанию, PVM считает, что только машина, на которой вы работаете должна быть включена в машину PVM.
То есть, все процессы, которые вы создаёте, будут запланированы к запуску на
одной и той же машине. Команда
3.1 Демонстрационная ПрограммаТеперь, когда вы убедились, что система PVM была правильно установлена,
давайте посмотрим, как писать программы. Программы для PVM могут писаться на Си и на Фортране.
Мы будем использовать язык Си. Чтобы использовать систему PVM вы должны включить в программу несколько вызовов функций PVM. И слинковать (link) библиотеку PVM с вашими программами.
Для начала, давайте напишем простую программу, которая будет состоять из двух программ:
master и slave. Программа master будет посылать программе slave некоторую строку,
программа slave преобразует все символы строки в ПРОПИСНЫЕ (upper case) и перешлёт строку обратно программе master.
Ниже приведён код программ master и slave. Чтобы скомпилировать программы
дайте такую команду [Отсюда вы можете загрузить обе программы в виде 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)
Как только программы скомпилировались, вы должны скопировать их в директорию
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, нужно включить заголовочный файл
Рассмотрим программу master. Сначала мы получаем ID задачи, вызывая функцию
PVM
Сообщение в PVM состоит, в основном, из двух частей,
данные и тэг(tag), который идентифицирует тип сообщения. Тэг помогает нам различать
разные сообщения. Рассмотрим пример со сложением, который подробно будет далее:
предположим, вы ожидаете, что каждая программа slave будет посылать программе master целое число, которое является суммой элементов.
Возможна такая ситуация, что в одной из программ slave произойдёт ошибка, и мы захотим переслать программе master целое число, которое идентифицирует код ошибки.
Каким же образом программа slave определит, является ли полученное число промежуточным результатом или кодом ошибки?
Вот здесь на помощь и приходят тэги. Вы можете присвоить сообщению с промежуточным результатом тэг,
скажем,
Чтобы послать сообщение, сначала надо "инициализировать" буфер посылки.
Это делается вызовом функции
Как только данные посланы, программа slave обработает их и возвратит программе master, как мы увидим.
Теперь мы вызываем
Перед выходом, программа должна сообщить системе PVM,
что она завершается, и что можно освободить все занимаемые ею системные ресурсы.
Это делается при помощи вызова функции
В программе slave всё должно быть понятно. Сначала она находит ID задачи master'а
( который является родительским процессом для программы slave, т.к. он её и запустил),
вызывая функцию 3.3 Программа сложенияТеперь, когда вы знаете основы написания программ для PVM, воплотим в жизнь алгоритм сложения,
который мы разработали ранее. У нас будет один master и четыре программы slave.
Сначала master запустит четыре salve'а и предаст каждому свою часть данных.
Программы slave сложат данные и отправят результаты master'у. Для этого у нас есть два типа сообщений:
первый - когда 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 и помещает их в массив
Как только данные были распределены между программами slave,
master должен ждать пока программы slave вернут промежуточные результаты.
В одном случае master сначала получит результат от программы slave 0
(т.е. slave чей ID задачи хранится в В вызове функции
3.4 Работаем с PVMЕсли вы заинтересовались, можете подумать о некоторых задачах, для которых
вы можете написать параллельные программы. Конечно, ошибки неизбежны. В консоли PVM можно использовать команду
4. Реализация на базе MPIВ предыдущем разделе мы увидели реализацию программы сложения с использованием PVM. Теперь давайте рассмотрим другой подход, который может быть использован при разработке параллельных программ. Этот подход - использование библиотеки MPI. MPI означает Интерфейс передачи сообщений (Message Passing Interface). Это стандарт, разработанный чтобы позволить нам писать переносимые приложения с передачей сообщений. Он предоставляет функции для обмена сообщениями и многие другие функции. Обратите внимание, что если PVM - программная система, то MPI - стандарт, поэтому могут существовать многие его реализации. Мы будем использовать реализацию под названием LAM, что означает Локальный Мультикомпьютер (Local Area Multicomputer). Эта реализация так же доступна на CD с Red Hat Linux как RPM пакет, поэтому проблем с установкой быть не должно.
После того, как вы установили RPM пакет, перейдите в директорию [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 Чтобы скомпилировать эту программу дайте команду [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 программа должна сначала вызвать функцию
Давайте рассмотрим функцию master.
После чтения значений a, b, c и d от пользователя, master должен послать a и b
slave 1, c и d slave 2. Вместо того, чтобы посылать переменные по отдельности, мы упакуем их в массив
и пошлём его. Всегда лучше упаковать все данные, которые вы хотите послать в одно сообщение, чем посылать несколько с индивидуальными данными.
Это сокращает временные расходы на передачу сообщений.
Как только буфер готов, в отличие от PVM нам не нужно паковать или кодировать данные,
MPI будет управлять кодированием изнутри. Поэтому мы можем непосредственно обратиться к функции
Как только данные были распределены между программами slave, master
должен ждать от них возвращения результата. Для простоты давайте сначала получим сообщение от
slave 1, а потом от slave 2. Чтобы получить сообщение, мы используем функцию
В этой программе код master, вместе с кодами программ
slave был в одном файле. Далее мы увидим, как можно использовать несколько исполняемых файлов.
Из makefile'а мы видим что, для того чтобы скомпилировать программу на MPI, есть программа
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
Чтобы скомпилировать программы наберите # Схема приложения для программы сложения с использованием 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, мы указываем источник сообщений как Если у вас есть сеть, соединённых между собой компьютеров, вы можете заставить программы запускаться на многих компьютерах, просто модифицируя файл схемы приложения. Вместо указания узла n0 в качестве хоста, укажите имя машины и количество процессов, которое вы хотите на ней запустить. За более подробной информацией по этой теме обращайтесь к man страницам и ссылкам в конце статьи.
5. ЗаключениеМы увидели как писать параллельные программы, используя библиотеки PVM и MPI. Т.к. эти библиотеки доступны на большом числе платформ и являются стандартом де-факто построения параллельных программ, если нужно программы, написанные с использованием PVM или MPI, будут работать с минимальными модификациями на огромном числе машин. В этой статье мы уделили особое внимание функциям обмена информацией типа точка - точка (point to point), которые предоставляют эти библиотеки, и их использованию в передаче сообщений. Кроме того, и PVM и MPI предоставляют некоторое количество расширенных функций, таких, как коллективная передача данных (broadcasting или multicasting), группы процессов и управления, редукционные функции и т.д. Я столько приветствую использование вами этих расширенных функций. Это свободное программное обеспечение позволяет вам использовать сеть соединённых между собой компьютеров, как один большой компьютер, поэтому если у вас есть какая-нибудь задача, требующая огромных вычислительных затрат, вы можете использовать компьютерную сеть вашего университета или офиса. Возможно, вам пригодятся эти книги, в них подробно написано, как всё это можно осуществить. А так же доступно огромное количество книг и обучалок чтобы помочь вам. Ниже я привожу список материалов, которыми пользовался сам.
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 |
Вернуться на главную страницу |