Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Хьюз Камерон. Параллельное и распределенное программирование на С++ - royallib.ru.doc
Скачиваний:
117
Добавлен:
11.03.2016
Размер:
1.97 Mб
Скачать

Методы использования pvm-задач

Работу, которую выполняет С++-программа, можно распределить между функциями, объектами или их сочетаниями. Действия, выполняемые программой, обычно делятся на такие логические категории: операции ввода-вывода, интерфейс пользователя, обработка базы данных, обработка сигналов и ошибок, числовые вычисления и т.д. Отделяя код интерфейса пользователя от кода обработки файлов, а также код процедур печати от кода числовых вычислений, мы не только распределяем работу програ м мы между функциями или объектами, но и стараемся выделять категории действий в соответствии с их характером. Логические группы организуются в библиотеки, модули, объектные шаблоны, компоненты и оболочки. Такой тип организации мы поддерживае м и при внесении PVM-задач в С++-програ мм у. Мы може м подойти к деко м позиции работ (work breakdown structure), используя м етод либо восходя щ его, либо нисходя щ его проектирования. В любом случае параллелиз м должен естественно вписываться в работу, которая на м ечена для выполнения функцией, модулем или объектом.

Не самая удачная идея — попытаться директивно навязать параллелиз м програ мм е. Искусственно насаждае м ый параллелиз м является причиной фор м ирования гро м оздкой архитектуры, которая, как правило, трудна для пони м ания и поддержки и создает сложности при определении корректности програ мм ы. Поэто м у, если програ мм а использует PVM-задачи, они должны быть результато м естественного разбиения программы. Каждую PVM-задачу следует отнести к одной из функциональных категорий. Например, если м ы разрабатывае м приложение, которое содержит обработку данных на естественном языке (Natural Language Processing — NLP), м еханиз м речевого воспроизведения текста (text-to-speech engine — TTS-engine) как часть интерфейса пользователя и формирование логических выводов как часть выборки данных, то параллелизм (естественный для NLP-компонента) должен быть представлен в виде задач внутри NLP-модуля или объекта, который отвечает за NLP-обработку. Аналогично параллелизм внутри компонента фор м ирования логических выводов следует представить в виде задач, составляю щ их модуль (объект или оболочку) выборки данных, отвечаю щ ий за выборку данных. Другими словами, мы идентифицируем PVM-задачи там, где они логически вписываются в работу, выполняемую программой, а не просто разбиваем работу программы на набор некоторых об щ их PVM-задач.

Соблюдение первичности логики и вторичности параллелизма имеет несколько последствий для С++-программ. Это означает, что мы могли бы порождать PVM-задачи из функции main () или из функций, вызываемых из функции main () (и даже из других функций). Мы могли бы порождать PVM-задачи из методов, прина д лежащих объектам. Место порождения задач зависит от требований к параллельности, выдвигаемых соответствую щ ей функцией, модулем или объектом. В об щ ем случае PVM-задачи можно разделить на две категории: SPMD (производная от SIMD) и MPMD (производная от MIMD). В модели SPMD все задачи будут выполнять одинаковый набор инструкций, но на различных наборах данных. В модели MPMD все задачи будут выполнять различные наборы инструкций на различных наборах данных. Но какую бы модель мы не использовали (SPMD или MPMD), создание задач должно происходить в соответствую щ их областях программы. Некоторые возможные конфигурации для порождения PVM-задач показаны на рис. 6.4.

Реализация модели SPMD (SIMD) c помощью PVM-и С++-средств

Вариант 1 на рис. 6.4 представляет ситуацию, при которой функция main () порождает от 1 до N задач, причем каждая задача выполняет один и тот же набор инструкций, но на различных наборах данных. Су щ ествует несколько вариантов реализации этого сценария. В листинге 6.1 показана функция main (), которая вызывает функцию pvm_spawn().

// Листинг б.1. Вызов функции pvm_spawn() из // функции main()

int main(int argc, char *argv[]) {

int TaskId[10]; int TaskId2[5]; // 1-е порождение:

pvm_spawn(«set_combination»,NULL,0,"",10,TaskId);

// 2-е порождение:

pvm_spawn(«set_combination», argv, 0,"",5,TaskId2); //. . .

}

В листинге 6.1 при первом порождении создается 10 задач. Каждал задача будет выполнять один и тот же набор инструкций, содержа щ ихся в программе set_combination. При успешном выполнении функции pvm_spawn () массив TaskId будет содержать идентификаторы PVM-задач. Если про г ра мм а в листин г еб.1 имеет идентификатор TaskIds, то она может использовать функции pvm_send( ) для отправки данных, под г отовленных д л я обработки каждой про г раммой. Это воз м ожно б л а г одаря то м у, что функция pvm_send () содержит идентификатор задачи-получате л я.

При второ м порождении (с м. листин г б.1) создается пять задач, но в это м случае каждой задаче с по м о щ ью пара м етра argv передается необходи м ал информация. Это — дополнительный способ передачи информации задачам при их запуске. Тем самы м сыновние задачи получают е щ е одну воз м ожность уникальны м образо м идентифицировать себя с по м о щ ью значений, получае м ых в пара м етре argv. В листин г е 6 .2, чтобы создать N задач, функция main () несколько раз (вместо одно г о) обра щ ается к функции pvm_spawn ().

// Листинг 6.2. Использование нескольких вызовов

// функции pvm_spawn() из функции main()

int main(int argc, char *argv[]) {

int Taskl; int Task2; int Task3; //.. .

pvm_spawn(«set_combination», NULL,1,«hostl»,l,&Taskl); pvm_spawn(«sec_combination»,argv,1,«host2»,1, &Task2); pvm_spawn(«set_combination»,argv++,l,«host3»,l,&Task3); //. . .

}

Подход к созданию задач, продемонстрированный в листин г е 6.2, можно использовать в том случае, ко г да нужно, чтобы задачи выполнялись на конкретных компьютерах. В этом состоит одно из достоинств PVM-среды. Ведь про г рамме ино г да стоит воспользоваться преимуществами некоторых конкретных ресурсов конкретно г о компьютера, например, специальным математическим спецпроцессором, процессором графическо г о устройства вывода или какими-то дру г ими возможностями. В листин г е 6.2 обратите внимание на то, что каждый компьютер выпол н яет один и тот же набор инструкций, но все они получили при этом разные ар г ументы командной строки. Вариант 2 (см. рис. 6.4) представляет сценарий, в котором функция main( ) не порождает PVM-задачи. В этом сценарии PVM-задачи ло г ически связаны с функцией funcB (), и поэтому здесь именно функция funcB () порождает PVM-задачи. Функциям main( ) и funcA( ) нет необходимости знать что-либо о PVM-задачах, поэтому им и не нужно иметь соответствую щ ий PVM-код. Вариант 3 (см. рис. 6.4) представл я ет сценарий, в котором функции main () и дру г им функциям в про г рамме прису щ естественный параллелизм. В этом случае роль «дру г их» функций и г рает функция funcA (). PVM-задачи, порождаемые функциями main () и funcA (), выполняют различный код. Несмотря на то что задачи, порожденные функцией main (), выпол н яют идентичный код, и задачи, порожденные функцией funcA (), выполняют идентичный код, эти два набора задач совершенно различны. Этот вариант иллюстрирует возможность C++-про г раммы использовать коллекции задач для о д новременно г о решения различных проблем. Ве д ь не су щ ествует причины, по которой на про г рамму бы нала г алось о г раничение решать в любой момент времени только о д ну проблему. Вариант 4 (см. рис. 6 .4) пре д ставляет случай, ко гд а параллелизм заключен внутри объекта, поэтому порождение PVM-задач реализует один из методов это г о объекта. Этот вариант показывает, что при необхо д имости параллелизм может исходить из класса, а не из «свободной» функции.

Как и в дру г их вариантах, все PVM-задачи, порожден н ые в варианте 4, выполняют одинаковый набор инструкций, но с различными данны м и. Этот SPMD етод (Single Program, Multiple-Data — одна програ м ма, множество потоков данных) часто используется для реализации параллельного решения проблем некоторого типа. И то, что язык С++ обладает по д держкой объектов и средств обоб щ енного программирования на основе шаблонов, делает его основным инстру м енто м при решении подобных задач. Объекты и шаблоны позволяют С++-программисту представлять обоб щ енные игибкие решения для различных проблем с помо щ ью одной-единственной программной единицы. Наличие единой программной единицы прекрасно вписывается в модель параллелиз м а SPMD. Понятие класса расширяет модель SPMD, позволяя решать целый класс пробле м. Шаблоны дают воз м ожность решать определенный класс проблем для практически любого типа данных. Поэтому, хотя все задачи в модели SPMD выполняют один и тот же код (програ мм ную единицу), он м ожет быть предназначен для любого объекта или л юбого из его пото м ков и рассчитан на раз л ичные типы данных (азначит, и на различные объекты!). Напри м ер, в листингеб.З используется четыре PVM-задачи для генерирования четырех множеств, в каждом из которых имеется C(n,r) элементов: C(24,9), C(24,12), C(7,4) и C(7,3). В частности, влистинге 6.3 перечисляются возможные сочетания из 24 цветов, взятые по 9 и по 12. Здесь также перечисляются возможные сочетания из 7 чисел с плаваю щ ей точкой, взятые по 4 и по 3. Пояснения пообозначению C(n,r) приведены в разделе $ 6.1 («Обозначение сочетаний»).

// Листинг б.З. Создание сочетаний из заданных множеств

int main(int argc,char *argv[]) {

int RetCode,TaskId[4];

RetCode = pvm_spawn («pvm_generic_combination11, NULL, 0, "", 4,TaskId);

if(RetCode == 4) {

colorCombinations (TaskId[0] , 9) ; colorCombinations(TaskId[l] ,12) ; numericCombinations(TaskId[2],4); numericCombinations(TaskId[3],3); saveResult(TaskId[0]); saveResult(TaskId[l]); saveResult(TaskId[2]); saveResult(TaskId[3]); pvm_exit() ;

}

else{

cerr « «Ошибка при порождении сыновнего процесса.»

« endl; pvm_exit() ;

}

return(0);

}

В листинге 6.3 обратите внимание на порождение четырех PVM-задач: pvm_spawn(«pvm_generic_combination» ,NULL, 0, н » ,4,TaskId) ;

Каждая порожденнал задача должна выполнять програ м му с именем pvm _generic_combination. Аргу м ент NULL в вызове функции pvm_spawn() означает, что через параметр argv[] не передаются никакие опции. Значение 0 в вызове функции pvm_spawn () свидетельствует, что нас не беспокоит, на каком ко м пьютере будет выполняться наша задача. Аргу м ент TaskId представляет м ассив, предназначенный для хранения четырех целочисленных значений, который при условии успешного выполнения функции pvm_spawn () будет содержать идентификаторы каждой порожденной PVM-задачи. В листингеб.З обратите также вни м ание на вызов функций colorCombinations () и numericCombinations (). Они «дают работу» PVM-задачам. Определение функции colorCombinations () представлено в листинге 6.4.

// Листинг 6.4. Определение функции colorCombinations()

void colorCombinations(int TaskId,int Choices) {

int MessageId =1; char *Buffer; int Size; int N;

string Source(«blue purple green red yellow orange

silver gray "); Source.append(«pink black white brown light_green

aqua beige cyan "); Source.append(«olive azure magenta plum orchid violet

maroon lavender»); Source. append (" \n**) ;

Buffer = new char[(Source.size() + 100)]; strcpy(Buffer,Source.c_str()); N = pvm_initsend(PvmDataDefault); pvm_pkint(&Choices,1,1); pvm_send(TaskId,MessageId); N = pvm_initsend(PvmDataDefault); pvm_pkbyte(Buffer,strlen(Buffer),1); pvm_send(TaskId,MessageId); delete Buffer;

}

В листингеб.З от м етьте два обращения к функции colorCombinations(). Каждое из них велит PVM-задаче перечислить различное количество сочетаний цветов: C(24,9) и C(24,12). Первал PVM-задача д олжна сгенерировать 1 307 504 цветовых сочетаний, а вторал — 2 704 156. Эту работу выполняет програм м а, заданнал в вызове функции pvm_spawn (). Каждый цвет представляется строкой. Следовательно, програ мм а pvm_generic_combination (с по м ощью функции colorCombinations()) генерирует сочетания цветов, используя в качестве входных данных набор строк. Но когда она орулует «рука м и» функции numericCombinations (), показанной в листинге 6.5, в качестве входных данных используется набор чисел с плавающей точкой. Код листинга 6.3 также содержит два вызова функции numericCombinations (). Первый генерирует C(7,4) сочетаний, а второй — C(7,3).

// Листинг 6.5. Использование PVM-задач для генерирования //   сочетаний чисел

void numericCombinations(int TaskId,int Choices) {

int MessageId = 2; int N;

double ImportantNumbers[7] =

{3.00e+8,6.67e-ll,1.99e+30,

6.2. Библио т ека PVM для языка С++ 229

1.67e-27,6.023e+23,6.63e-34,

3.14159265359}; N = pvm_initsend(PvmDataDefault); pvm_pkint(&Choices,1,1) ; pvm_send(TaskId,MessageId) ; N = pvm_initsend(PvmDataDefault); pvm_pkdouble (ImportantNumbers, 5,1) ; pvm_send(TaskId,MessageId) ;

}

В функции numericCombinations () из листинга 6.4 PVM-задача использует м ассив чисел с плаваю щ ей точкой, а не м ассив байтов, представл я ю щ их строки. Поэто м у функция colorCombinations() отправл я ет свои данные PVM-задача м с по м о щ ью вызовов таких функций:

pvrt_pkbyte(Buffer,strlen(Buffer) ,1) ; pvm_send(TaskId,MessageId) ;

А функция numericCombination( ) отправляет свои данные PVM-задача м таки м образом:

pvm_pkdouble (ImportantNumbers, 5,1) ; pvn_send(TaskId,MessageId) ;

Функция colorCombinations() в листинге6.4 создает строку названий цветов, азатем копирует ее в м ассив Buffer типа char. Этот м ассив затем упаковывается и отправляется PVM-задаче с помо щ ью функций pvm_pkbyte () и pvm_send (). Функция numericCombinations() в листинге6.5 создает массив типа double и отсылает его PVM-задаче с помо щ ью функций pvm_pkdouble () и pvm_send( ). Одна функция отправляет символьный массив, а другая — массив типа double. В обоих случалх PVM-задачи выполняют одну и туже программу pvm_generic_combination. Именно здесь нас выручает преиму щ ество использования С++-шаблонов. Одинаковые задачи благодаря этому могут работать не только с различными данными, но и с различными типами данных без изменения самого кода. Использование шаблонов в С++ позволяет сделать модель SPMD более гибкой и эффективной. Программе pvm_generic_combination практически безразлично, с какими типами данных ей придется работать. Использование контейнерных С++-классов позволяет генерировать любые комбинации векторов (vector<T>) объектов. Программа pvm_generic_combination «не знает», что она будет работать с двумя типами данных. В листин г е 6.6 представлен раздел кода из программы pvm_generic_combination.

// Листинг 6.6. Использование тега MessageId для //   распознания типов данных

pvm_bufinfo (N, &NumBytes, &MessageId, &Ptid) ; if(MessageId == 1){

vector<string> Source;

Buf = new char[NumBytes];

pvm_upkbyte(Buf, NumBytes,1);

strstream Buffer;

Buffer « Buf « ends,-

while(Buffer.good())

{

Buffer » Color;

if(!Buffer.eof()){

Source.push_back(Color);

}

}

generateCombinations<string>(Source, Ptid,Value); delete Buf;

}

if(MessageId == 2){

vector<double> Source; double *ImportantNumber; NumBytes = NumBytes / sizeof(double); ImportantNumber = new double[NumBytes]; pvm_upkdouble(ImportantNumber, NumBytes,1); copy(ImportantNumber,ImportantNumber +(NumBytes + 1), inserter(Source, Source.begin())); generateCombinations<double>(Source, Ptid,Value); delete ImportantNumber;

}

Здесь используется тег MessageId, позволяю щ ий распознать, с каким типом данных м ы работае м. Но в С++ воз м ожно более удачное решение. Если тег MessageId содержит число 1, значит, м ы работае м со строка м и. Следовательно, м ожно сделать сле-лую щ ее объявление: vector<string> Source;

Если тег MessageId содержит число 2, то м ы знае м, что должны работать с числа м и с плаваю щ ей точкой, и поэто м уделае м такое объявление: vector<double> Source;

Объявив, какого типа данные будет содержать вектор Source, остальную часть функции в програ мм е pvm_generic_combination можно легко обоб щ ить. В листин- r e 6.6 обратите вни м ание на то, что каждая инструкция if() вызывает функцию generateCombinations (), которая является шаблонной. Эта шаблоннал архитектура позво л яет достичь такой степени универсальности, которая распростра н яет сце н арии SPMD и MPMD на наши PVM-програ мм ы. Мы вер н е м ся к обсуждению нашей програм м ы pvm_generic_combination после расс м отрения базовых механизмов PVM-среды. Важно отметить, что контейнерные С++-классы, потоковые классы и шаблонные алгорит м ы значительно усиливают гибкость PVM-програ мм ирования, которую невозможно было бы так просто реализовать в других PVM-средах. Именно такая гибкость создает воз м ожности д л я построения высокоорганизованных и элегантных парал л е л ьных архитектур.

Реализация модели MPMD (MIMD) с помощью PVM-и С++-средств

В то время как м оде л ь SPMD испо л ьзует функцию pvm_spawn () д л я создания некоторого чис л а задач, выпо л няю щ их одну и ту же програ мм у, но на потенциально раз л ичных наборах данных и л и ресурсов, м оде л ь MPMD испо л ьзует функцию pvm_spawn () д л я создания задач, которые выпо л няют раз л ичные програм м ы на раз л ичных наборах данных. Как с помо щ ью одной С++-программы реализовать модель MPMD (на основе PVM-функций), показано в л истинге 6.7.

6.2. Библиотека PVM для языка С++ 231

// Листинг 6.7. Использование PVM для реализации //   MPMD-модели вычисления

int main(int argc, char *argv[]) {

int Taskl[20]; int Task2[50]; int Task3[30]; //...

pvm_spawn («pvm_generic_combination», NULL, 1,

«hostl»,20,Taskl); pvm_spawn («generate_plans», argv, 0, "", 50, Task2) ; pvm_spawn(«agent_filters»,argv++,l, «host 3»,30,&Task3) ; //.. .

}

При выполнении кода, представленного в листинге 6.7, создается 100 задач. Первые 20 задач генерируют сочетания. Слелующие 50 по мере создания сочетаний генерируют планы на их основе. Последние 30 задач отфильтровывают самые удачные планы из набора планов, сгенерированного предыдущи м и 50 задачами. Уже только это краткое описание позволяет ощутить отличие модели MPMD от модели SPMD, в которой все программы, порожденные функцией pvm_spawn (), были одинаковы. Здесь же за работу, назначаемую PVM-задача м, «отвечают» програ мм ы pvm_generic_combination, generate_plans и agent_filters. Все эти задачи выполняются параллельно и работают с собственны м и набора м и данных, нес м отря на то что одни наборы являются результато м преобразования дру г их. Про г ра мм а pvm_generic_combination преобразует свой входной набор данных в набор, который зате м может использовать программа generate_plans. Программа generate_plans, в свою очередь, преобразует входной набор данных в набор, который может затем использовать программа agent_filters. Очевидно, что эти задачи должны обмениваться сообщениями. Эти сообщения представляют собой входную иуправляющую информацию, которая передается между процесса м и. Необходи м о также отметить, что в листинге 6.7 функция pvm_spawn () используется для размещения 20 задач pvm_generic_combination на компьютере с именем hostl. Задача generate_plans была размещена на 50 безымянных процессорах, но каждая из этих 50 задач получила при это м один и тот же аргу м ент ко м андной строки с по м ощью параметра argv. Задачи agent_filters также были направлены на конкретный ко м пьютер (с именем host 3), и каждая задача получила один и тот же аргумент командной строки посредством параметра argv. Этот пример — лишь еще одно подтверждение гибкости и мо щ и библиотеки PVM. Некоторые варианты реализации модели MPMD с использованием среды PVM показаны на рис. 6.5.

При желании мы можем воспользоваться преиму щ ествами конкрет н ых ресурсов конкретных компьютеров или же «положиться на судьбу» в виде «заказа» произвольных безымянных компьютеров. Мы можем также назначить рааличные виды работ различным задачам одновременно. На рис. 6.5 компьютер А представляет собой компьютер с массовым параллелизмом (МП-компьютер), а компьютер В осна щ ен некоторым количеством специализированных математических процессоров. Также отметьте, что PVM-среда в данном случае состоит из таких компьютеров, как PowerPCs, Spares, Crays и т.д. В одних случалх можно не беспокоиться о конкретных возможностях компьютеров в PVM-среде, а в дру г их требуется иной подход. Использование функции pvm_spawn () позволяет С++-программисту не указывать конкретный компьютер для решения задачи, когда это не важно. Но если вам известно, что компьютер осна щ ен специализированными средствами, то их можно эффективно использовать, определив соответствую щ ий параметр при вызове функции pvm_spawn ().

Рис. 6.5. Неко т орые вариан т ы модели MPMD дос т упны для реализации благодаря использованию среды PVM