Programming Taskbook


E-mail:

Пароль:

Регистрация пользователя   Восстановление пароля

 

ЮФУ SMBU

Электронный задачник по программированию

©  М. Э. Абрамян (Южный федеральный университет, Университет МГУ-ППИ в Шэньчжэне), 1998–2024

 

PT for MPI-2 | Разработка новых заданий | Примеры

Prev


Примеры разработки заданий по параллельному программированию

В данном разделе приводятся примеры разработки заданий по параллельному программированию с использованием конструктора учебных заданий PT4TaskMaker.

Создание новой группы учебных заданий и импортирование в нее имеющегося задания

Любая группа заданий должна оформляться в виде динамической библиотеки — dll-файла. Будем разрабатывать библиотеку с новой группой заданий в среде Visual Studio 2015. Назовем новую группу MPIDemo (в качестве префикса имени группы, связанной с MPI-программированием, необходимо указать текст «MPI», поскольку при наличии такого префикса в именах заданий модуль PT4Load будет создавать для этих заданий специализированные проекты-заготовки, предназначенные для выполнения задания в параллельном режиме). Все динамические библиотеки, содержащие группы заданий для электронного задачника Programming Taskbook, должны иметь имя, которое начинается с префикса PT4, за которым следует имя группы. Таким образом, наш проект должен иметь имя PT4MPIDemo.

Создадим заготовку проекта в подкаталоге рабочего каталога PT4Work, выполнив следующие действия:

  • начнем с вызова команды меню «File | New | Project…»;
  • в появившемся окне выберем раздел «Visual C++» и в нем вариант «Win32 Project»;
  • в качестве имени проекта укажем PT4MPIDemo, в качестве каталога размещения проекта укажем c:\PT4Work, снимем выделение с флажка «Create directory for solution», если этот флажок установлен;
  • после нажатия кнопки «ОК» на экране появится окно «Win32 Application Wizard», в котором следует сразу нажать кнопку «Next», установить в появившемся списке настроек вариант DLL и снять все флажки в разделе «Additional options», после чего нажать кнопку «Finish».

Созданный проект будет включать файл PT4MPIDemo.cpp, в котором мы и будем разрабатывать новые задания. Кроме того, в этот проект надо добавить файлы pt4taskmaker.cpp и pt4taskmaker.h, скопировав их из подкаталога PT4forMPI2\taskmaker системного каталога задачника (c:\Program Files (x86)\PT4). Для файла pt4taskmaker.cpp необходимо также выполнить команду меню «Project | Add Existing Item…».

Определяемая нами в файле PT4MPIDemo.cpp группа заданий должна включать ряд стандартных элементов. Перечислим эти элементы:

  • директива подключения заголовочного файла pt4taskmaker.h;
  • основная функция группы заданий, определяющая задание по его номеру (обычно она имеет имя InitTask); данная функция имеет один параметр целого типа num, не возвращает результаты и должна снабжаться модификатором _stdcall;
  • функция инициализации группы заданий, которая должна иметь фиксированное имя inittaskgroup (все буквы строчные); эта функция не имеет параметров, не возвращает результаты и также должна снабжаться модификатором _stdcall.

Для подключения заголовочного файла pt4taskmaker.h добавим в начало файла PT4MPIDemo.cpp следующую директиву:

#include "pt4taskmaker.h"

Функция InitTask(num) обеспечивает инициализацию задания c номером num. Обычно в ней используется оператор switch, в котором происходит выбор инициализирующей функции по значению параметра num.

Простейшим способом добавить задание в новую группу является импортирование задания из уже существующей группы. Для этого в конструкторе предусмотрена функция UseTask с двумя параметрами: первый параметр (типа const char* или std::string) задает имя существующей группы, из которой импортируется задание, второй параметр (типа int) — номер импортируемого задания. Никаких особых действий, связанных с загрузкой указанной группы заданий, выполнять не требуется, поскольку задачник выполняет эту загрузку автоматически. Воспользуемся этой функцией и импортируем в нашу группу второе задание из первой группы MPI1Proc, входящей в задачник PT for MPI-2. В результате основная функция группы заданий должна принять следующий вид:

void _stdcall InitTask(int num)
{
  switch (num)
  {
    case 1:
      UseTask("MPI1Proc", 2);
      break;
  }
}

Осталось реализовать функцию инициализации группы заданий inittaskgroup. В этой функции должна вызываться функция CreateGroup, в которой задаются настройки создаваемой группы: имя группы, ее описание, сведения об авторе, строковый ключ (последовательность произвольных символов), число заданий и имя основной функции группы.

По умолчанию группа считается доступной для всех языков программирования, поддерживаемых задачником Programming Taskbook. Поскольку задачник PT4 for MPI-2 ориентирован только на язык C++, необходимо добавить в функцию inittaskgroup фрагмент, позволяющий сделать группу доступной только для указанных языков (для этого следует проанализировать возвращаемое значение функции CurrentLanguage, позволяющей определить выбранный для задачника язык программирования. Приведем определение функции inittaskgroup для нашей группы заданий:

void _stdcall inittaskgroup()
{
  if ((CurrentLanguage() & lgCPP) == 0)
    return;
  CreateGroup("MPIDemo",
    "Примеры задач по параллельному программированию",
    "М. Э. Абрамян, 2017", "sddwertfghklbfdgfgd", 1, InitTask);
}

Условный оператор в функции inittaskgroup обеспечивает немедленный выход из этой функции (без выполнения инициализации группы заданий) в случае, если текущий язык не является языком C++.

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

Поскольку наш проект является библиотекой, а не исполняемым приложением, запустить его на выполнение нельзя. Для тестирования динамических библиотек удобно использовать управляющее приложение (host application), которое при запуске подключает библиотеку и тем самым позволяет проверить правильность ее работы. При тестировании библиотеки с учебными заданиями наиболее подходящим вариантом управляющего приложения является программный модуль PT4Demo.exe, входящий в состав задачника Programming Taskbook и позволяющий отобразить на экране любое задание группы в демонстрационном режиме. С помощью параметров командной строки программу PT4Demo можно настроить на немедленное отображение определенного задания требуемой группы. Не приводя здесь все возможные варианты этих параметров, укажем те, которые следует указать для тестирования нашей библиотеки:

-gMPIDemo -n999

Первый из параметров определяет используемую группу заданий (MPIDemo), а второй обеспечивает отображение на экране последнего из заданий этой группы.

Для определения управляющего приложения и его параметров в среде Visual Studio надо выполнить следующие действия:

  • начните с команды меню «Project | PT4MPIDemo Properties...»;
  • выберите в появившемся окне вариант «Release» в качестве значения настройки «Configuration»;
  • перейдите в раздел «Debugging» и заполните значения трех первых настроек следующим образом (мы предполагаем, что задачник Programming Taskbook размещен в каталоге c:\Program files (x86)\PT4):
Command c:\Program files (x86)\PT4\PT4Demo.exe
Command Arguments -gMPIDemo -n999
Working Directory .\Release

После этого закройте окно свойств, нажав кнопку «ОК».

Теперь мы можем протестировать заготовку нашей библиотеки. При запуске нашей программы (например, по нажатию клавиши F5) она должна успешно откомпилироваться, после чего на экране должно появиться окно модуля PT4Demo с выбранной группой MPIDemo, а также окно задачника с заданием MPIDemo1 в демонстрационном режиме:

Задание MPIDemo1 будет совпадать с заданием MPI1Proc2. Возможность подобного импортирования заданий полезна, если требуется исключить некоторые задания (слишком простые или слишком сложные), изменить порядок их следования или объединить в одной группе задания из нескольких имеющихся групп (например, для создания группы, используемой при проведении проверочной работы).

Разработка простого задания: MPIDemo2

Первое задание, которое мы реализуем явным образом, относится к той же начальной теме «Процессы и их ранги», что и задание MPI1Proc2, уже импортированное в разрабатываемую группу.

MPIDemo2. В главном процессе коммуникатора MPI_COMM_WORLD прочесть целое число и вывести его противоположное значение. В каждом подчиненном процессе того же коммуникатора прочесть вещественное число и вывести значение этого числа, умноженное на ранг процесса.

При выполнения этого задания не требуется выполнять пересылку данных. Тем не менее при реализации этого задания нам придется использовать все новые средства конструктора заданий, связанные с параллельным программированием («параллельный» вариант функции CreateTask и функцию SetProcess), и базовые средства конструктора, связанные с определением формулировки задания и входящих в него исходных и контрольных данных (причем как целого, так и вещественного типа).

Предварительно определим вспомогательную функцию, которая упростит действия по добавлению к заданию комментариев вида «Процесс <номер процесса>:»:

#include <string>

std::string prc(int n)
{
  return "Процесс " + std::to_string(n) + ": ";
}

Реализуем новое задание в виде функции MPIDemo2:

void MPIDemo2()
{
  int count = RandomN(3, 5);
  CreateTask("Процессы и их ранги", &count);
  if (count == 0)
    return;
  TaskText(
    "В главном процессе коммуникатора MPI\\_COMM\\_WORLD прочесть целое число\n"
    "и вывести его противоположное значение. В каждом подчиненном процессе\n"
    "того же коммуникатора прочесть вещественное число и вывести\n"
    "значение этого числа, умноженное на ранг процесса."
  );
  SetProcess(0);
  int n = RandomR(-99, 99);
  DataN(prc(0), n, 0, 1, 7);
  ResultN(prc(0), -n, 0, 1, 7);
  for (int i = 1; i < count; ++i)
  {
    SetProcess(i);
    double d = RandomR(-99, 99);
    DataR(prc(i), d, 0, i + 1, 7);
    ResultR(prc(i), d * i, 0, i + 1, 7);
  }
}

Необходимо также откорректировать функцию InitTask, включив в нее вызов функции MPIDemo2 (в дальнейшем функция InitTask будет дополняться вызовами функций, реализующих другие задания нашей группы):

void _stdcall InitTask(int num)
{
  switch (num)
  {
    case 1:
      UseTask("MPI1Proc", 2);
      break;
    case 2:
      MPIDemo2();
      break;
  }
}

Наконец, надо откорректировать предпоследний параметр функции CreateGroup, вызываемой в функции inittaskgroup, изменив его значение с 1 на 2:

  CreateGroup("MPIDemo",
    "Примеры задач по параллельному программированию",
    "М. Э. Абрамян, 2017", "sddwertfghklbfdgfgd", 2, InitTask);

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

Используя кнопки или клавиши «Пробел», Enter и Backspace, можно просматривать различные варианты исходных и контрольных данных для этого задания или переходить к другим заданиям группы MPIDemo.

Прокомментируем функцию MPIDemo2.

Первый оператор определяет (с помощью функции RandomN, входящей в конструктор PT4TaskMaker) количество процессов, используемое при текущем запуске задания. Возможное число процессов будет лежать в диапазоне от 3 до 5; для подобного простого задания такого количества процессов вполне достаточно — главное, чтобы при различных тестовых испытаниях программы количество процессов изменялось, пусть и в небольшом диапазоне.

Следующий оператор инициализирует задание, вызывая функцию CreateTask. Благодаря указанию количества процессов count в качестве второго параметра функции, задание будет выполняться в параллельном режиме.

Особое внимание надо обратить на следующий условный оператор:

  if (count == 0)
    return;

Для того чтобы понять его смысл, необходимо вспомнить механизм выполнения задания в параллельном режиме (см. раздел «Особенности работы задачника в параллельном режиме»). Экземпляр программы, запущенной непосредственно из интегрированной среды, является непараллельной программой-загрузчиком, которая определяет требуемое количество процессов и запускает параллельное приложение. После этого никакие действия, связанные с настройкой задания, ей выполнять не требуется. При выполнении параллельного приложения запускаются несколько экземпляров исходной программы (по одному экземпляру для каждого процесса), однако только в главном процессе (процессе ранга 0) определяются все данные, связанные с заданием; прочие процессы эти данные не формируют, а лишь получают от главного процесса. Таким образом, часть функции MPIDemo2, следующую за вызовом функции CreateTask, требуется выполнить только для главного процесса параллельного приложения, причем именно возвращаемое значение параметра count позволяет проверить, что данный экземпляр программы выполняется в главном процессе: в этом (и только в этом) случае параметр count отличен от нуля и, кроме того, равен числу запущенных процессов параллельного приложения. Для непараллельной программы-загрузчика и программ, запущенных в качестве подчиненных процессов параллельного приложения, параметр count вернет значение 0, означающее, что дальнейшие действия, определенные в функции MPIDemo2, выполнять не требуется. Входное значение параметра count в процессах параллельного приложения не используется; оно учитывается только в программе-загрузчике при запуске программы в параллельном режиме, а также при демо-запуске задания.

Программа, выполняющая демо-запуск задания, является непараллельной, как и программа-загрузчик (именно в таком непараллельном режиме работает, в частности, программный модуль PT4Demo, используемый нами для предварительного тестирования создаваемых заданий). Только при демо-запуске задания можно гарантировать, что входное значение параметра count будет равно его выходному значению.

Следует заметить, что отсутствие оператора if (count == 0) return; не скажется на предварительном тестировании задания, так как при запуске задания в демо-режиме возвращаемое значение параметра count никогда не будет равно 0. Однако при попытке запуска этого задания в параллельном режиме в случае отсутствия данного оператора будет выведено сообщение об ошибке: «В ситуации, когда параметр ProcessCount функции CreateTask вернул значение 0, не выполнен немедленный выход из функции инициализации задания».

Рассмотрим оставшиеся операторы функции MPIDemo2.

С помощью функции TaskText задается формулировка задания (обратите внимание на то, что перед символом подчеркивания указывается экранированный символ «\»; это связано с тем, что «обычный» символ подчеркивания в формулировках заданий означает переход в режим нижних индексов). В строке, указанной в функции TaskText, с помощью символов '\n' явным образом указываются позиции, по которым она разбивается на отдельные строки при выводе в разделе с формулировкой задания.

С помощью функций DataN, DataR и ResultN, ResultR определяются исходные и результирующие данные (данные для подчиненных процессов определяются в цикле). Перед определением данных для каждого процесса вызывается функция SetProcess, определяющая текущий процесс задания (в качестве параметра функции указывается ранг этого процесса). Все последующие данные, определяемые в задании, связываются с текущим процессом. Напомним, что до первого вызова функции SetProcess текущим считается процесс ранга 0 (таким образом, оператор SetProcess(0); можно было не указывать). В нашем случае с каждым процессом связывается один элемент исходных данных (целое или вещественное число) и один элемент результирующих данных (соответственно, целое число, противоположное исходному, или вещественное число, умноженное на ранг подчиненного процесса). Элементы исходных данных генерируются с помощью функций RandomN и RandomR, возвращающих соответственно целое и вещественное число, лежащее в указанном диапазоне. Опишем параметры функций DataN, DataR, ResultN, ResultR:

  • комментарий к определяемому элементу данных (этот параметр может отсутствовать),
  • значение элемента данных,
  • горизонтальная координата X, определяющая позицию вывода элемента в соответствующем разделе окна задачника (разделе исходных данных для функций DataN и DataR и разделе результатов и примера верного решения для функций ResultN и ResultR); X может лежать в диапазоне от 1 до 78; кроме того, для этого параметра предусмотрены три особых значения: 0, означающее центрирование данного элемента по всей ширине раздела, 100 — центрирование элемента по левой половине раздела, и 200 — центрирование по правой половине; можно также использовать константы xCenter = 0, xLeft = 100 и xRight = 200;
  • вертикальная координата Y, определяющая номер строки соответствующего раздела, в которой будет выведен элемент (строки нумеруются от 1);
  • ширина области вывода элемента (комментарий располагается слева от области вывода; элементы выравниваются по правой границе области вывода).

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

В конце функции, определяющей задание, можно настроить количество успешных тестовых испытаний (от 3 до 9), необходимое для того, чтобы данное задание считалось выполненным (успешные тестовые испытания должны быть проведены подряд). Для этого предназначена функция SetTestCount. Если данная функция не вызвана (как в нашем случае), то количество тестовых испытаний полагается равным 5.

Заканчивая обзор функции MPIDemo2, заметим, что при отсутствии функций SetProcess вид окна задачника в демонстрационном режиме не изменится, и никакого сообщения об ошибке выведено не будет. Однако в этом случае все исходные и результирующие данные будут связаны с процессом ранга 0, что приведет к противоречию с формулировкой задания. Чтобы выявить ошибки такого рода, необходимо попытаться выполнить разработанное задание, запустив его в параллельном режиме.

Библиотеки с дополнительными группами заданий задачник ищет в рабочем каталоге учащегося и в специальном подкаталоге LIB своего системного каталога (обычно системным каталогом задачника является каталог C:\Program Files (x86)\PT4). Чтобы не выполнять дополнительных действий по копированию файла PT4MPIDemo.dll в подкаталог LIB, можно настроить каталог Release, в котором создается данный файл, в качестве еще одного рабочего каталога задачника (используя, например, программу PT4Setup). После такой настройки в каталоге Release появится ярлык Load.lnk, позволяющий создать заготовки для учебных заданий. Заметим, что благодаря префиксу «MPI» в имени MPIDemo созданные проекты-заготовки для заданий группы MPIDemo будут учитывать особенности заданий по параллельному MPI-программированию (в частности, к проектам будут подключены необходимые файлы библиотеки MPI). Мы не будем останавливаться на описании решения задания MPIDemo2, поскольку оно очень похоже на решение задания MPI1Proc2 (см. раздел «Выполнение задания»).

Разработка задания, связанного с пересылкой данных: MPIDemo3

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

MPIDemo3. Количество процессов K является четным, в каждом процессе дано целое число. Переслать числа из всех процессов четного ранга (0, 2, …, K – 2) в процесс 0, а числа из всех процессов нечетного ранга (1, 3, …, K – 1) — в процесс 1. В процессах 0 и 1 вывести полученные числа в порядке возрастания рангов переславших их процессов.

По сравнению с предыдущим заданием здесь желательно использовать большее количество процессов, например, 6, 8 или 10. В такой ситуации целесообразно расположить данные о процессах в два столбца. При этом группировка процессов с четными и нечетными рангами по разным экранным столбцам придаст заданию дополнительную наглядность.

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

Приведем вид окна задачника для задания MPIDemo3 и одноименную функцию, реализующую это задание:

void MPIDemo3()
{
  int count = 2 * RandomN(3, 5);
  CreateTask("Создание новых коммуникаторов", &count);
  if (count == 0)
    return;
  TaskText(
    "Количество процессов~{K} является четным, в каждом процессе дано целое число.\n"
    "Переслать числа из всех процессов четного ранга (0, 2,~\\., {K}\\,\\-\\,2) в процесс~0,\n"
    "а числа из всех процессов нечетного ранга (1, 3,~\\., {K}\\,\\-\\,1)~\\= в процесс~1.\n"
    "В процессах 0 и 1 вывести полученные числа в порядке возрастания рангов\n"
    "переславших их процессов."
  );
  int k = count / 2;
  ResultComment("Процесс 0:", 0, 1);
  ResultComment("Процесс 1:", 0, 3);
  for (int i = 0; i < k; ++i)
  {
    int d = RandomN(10, 99);
    SetProcess(2 * i);
    DataN(prc(2 * i), d, xLeft, i + 1, 2);
    SetProcess(0);
    ResultN(d, Center(i + 1, k, 2, 2), 2, 2);
    d = RandomN(10, 99);
    SetProcess(2 * i + 1);
    DataN(prc(2 * i + 1), d, xRight, i + 1, 2);
    SetProcess(1);
    ResultN(d, Center(i + 1, k, 2, 2), 4, 2);
  }
}

Прокомментируем приведенную функцию.

Задание отнесено к подгруппе «Создание новых коммуникаторов» (это имя указано в качестве первого параметра функции CreateTask), так как наиболее эффективное его решение связано с применением вспомогательных коммуникаторов.

В тексте формулировки задания используется ряд управляющих последовательностей (команд): фигурные скобки выделяют имена переменных, символ «~» обозначает стандартный неразрывный пробел, команда «\,» — малый неразрывный пробел, команда «\=» обозначает тире (—), команда «\–» — знак «минус» (–), команда «\.» — многоточие (…). Некоторые из этих последовательностей учитываются только при выводе формулировки задания в html-документе (см. завершующую часть данного раздела). В частности, в html-документе все переменные выделяются курсивом. Если в управляющую последовательность входит символ «\», то его необходимо экранировать.

Для вывода комментариев, не связанных с конкретным элементом данных, предусмотрены функции DataComment и ResultComment; последнюю из этих функций мы использовали в нашем задании.

В функциях DataN мы применили горизонтальное центрирование данных относительно левой или правой половины раздела, указав в качестве параметра X константы xLeft и xRight соответственно. Напомним, что для центрирования относительно всего раздела можно использовать константу xCenter, но проще явно указать ее значение, равное 0 (мы использовали это значение в функциях ResultComment). В функциях ResultN применяется вспомогательная функция Center(I, N, W, B), обеспечивающая центрирование по горизонтали набора элементов: она возвращает позицию, с которой надо вывести I-й элемент набора из N элементов (I меняется от 1 до N) при условии, что ширина каждого элемента равна W позициям, а между соседними элементами надо указывать B пробелов.

Обратите внимание на то, что функцию SetProcess можно многократно вызывать с одним и тем же параметром.

После добавления в библиотеку PT4MPIDemo функции MPIDemo3 необходимо включить ее вызов в функцию InitTask:

void _stdcall InitTask(int num)
{
  switch (num)
  {
    case 1:
      UseTask("MPI1Proc", 2);
      break;
    case 2:
      MPIDemo2();
      break;
    case 3:
      MPIDemo3();
      break;
  }
}

Кроме того, в вызове функции CreateGroup следует положить равным 3 значение предпоследнего параметра, определяющего количество заданий в группе.

Для проверки правильности разработанного задания следует выполнить его. Как уже было отмечено выше, при решении целесообразно использовать вспомогательные коммуникаторы (приведем только заключительную часть функции Solve, которая должна располагаться после операторов, автоматически добавляемых в проект-заготовку):

MPI_Comm c;
MPI_Comm_split(MPI_COMM_WORLD, rank % 2, rank, &c);
int n, res[5];
pt >> n;
MPI_Gather(&n, 1, MPI_INT, res, 1, MPI_INT, 0, c);
if (rank < 2)
  for (int i = 0; i < size / 2; ++i)
    pt << res[i];

Вначале с помощью функции MPI_Comm_split исходный коммуникатор расщепляется на два, каждый из которых связывается с процессами одинаковой четности. Затем с помощью функции MPI_Gather данные из всех процессов, входящих в новые коммуникаторы, пересылаются в начальный процесс каждого коммуникатора. В исходном коммуникаторе MPI_COMM_WORLD начальные процессы двух созданных коммуникаторов имеют ранг 0 и 1, поэтому для определения процессов, в которых надо вывести полученные данные, достаточно использовать условие rank < 2.

Разработка сложного задания: MPIDemo4

В качестве последнего примера рассмотрим достаточно сложное задание, требующее применения различных средств библиотеки MPI. Данное задание можно отнести к подгруппе «Параллельное умножение матрицы на вектор».

MPIDemo4. В процессе 0 дано число N и вектор b размера N с вещественными элементами. В остальных процессах даны строки вещественной квадратной матрицы A порядка N, причем в каждом процессе вначале указывается число строк K, затем для каждой строки указывается ее порядковый номер в матрице и элементы этой строки. Найти произведение Ab и вывести его элементы в процессе 0.

Главной особенностью этого задания по сравнению с ранее рассмотренными является большое число исходных данных, которые надо определить для каждого процесса. Для повышения наглядности следует разбить данные для каждого процесса на несколько экранных строк, отведя одну строку для вывода комментария «Процесс <номер процесса>» и значения N (для главного процесса) или K (для подчиненных процессов), а последующие строки — для вывода данных, связанных с вектором b (для главного процесса) или с элементами соответствующих строк матрицы A (для подчиненных процессов).

Приведем вид окна задачника для задания MPIDemo4 и одноименную функцию, реализующую это задание, вместе со вспомогательной функцией Swap.

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

void Swap(int &a, int &b)
{
  int x = a;
  a = b;
  b = x;
}
void MPIDemo4()
{
  int count = RandomN(3, 6);
  CreateTask("Параллельное умножение матрицы на вектор",
&count);
  if (count == 0)
    return;
  TaskText(
    "В процессе 0 дано число {N} и вектор {b} размера {N} с вещественными элементами.\n"
    "В остальных процессах даны строки вещественной квадратной матрицы {A} порядка {N},\n"
    "причем в каждом процессе вначале указывается число строк {K}, затем для каждой\n"
    "строки указывается ее порядковый номер в матрице и ее элементы (строки\n"
    "нумеруются от~0). Найти произведение {Ab} и вывести его элементы в процессе~0."
  );

  // Формирование матрицы a и вектора b; n - порядок матрицы
  double a[10][10], b[10];
  int num[10];
  int n = RandomN(6, 9);
  for (int i = 0; i < n; ++i)
  {
    b[i] = RandomR(0, 9.9);
    for (int j = 0; j < n; ++j)
      a[i][j] = RandomR(0, 9.9);
    num[i] = i;
  }

  // Вычисление произведения Ab в массиве c
  double c[10];
  for (int i = 0; i < n; ++i)
  {
    c[i] = 0;
    for (int j = 0; j < n; ++j)
      c[i] += a[i][j] * b[j];
  }

  // Получение в массиве num случайного порядка
  // номеров строк матрицы
  for (int i = 0; i < 2 * n; ++i)
    Swap(num[RandomN(0, n - 1)], num[RandomN(0, n - 1)]);

  // Определение числа строк матрицы, передаваемых каждому
  // процессу (в процесс i передаются k[i] строк матрицы)
  int k[10];
  do
  {
    k[count - 2] = n;
    for (int i = 0; i < count - 2; ++i)
    {
      k[i] = RandomN(1, 3);
      k[count - 2] -= k[i];
    }
  } while (k[count - 2] <= 0);

  // Вывод исходных и результирующих данных для процесса 0
  SetProcess(0);
  DataN("Процесс 0: N = ", n, 4, 1, 1);
  for (int i = 0; i < n; ++i)
    DataR(b[i], Center(i + 1, n, 4, 2), 2, 4);
  ResultComment("Процесс 0:", 0, 2);
  for (int i = 0; i < n; ++i)
    ResultR(c[i], Center(i + 1, n, 6, 2), 3, 6);

  // Вывод исходных данных для остальных процессов
  int y = 2;
  int cnt = -1;
  for (int i = 1; i < count; ++i)
  {
    SetProcess(i);
    DataN(prc(i) + "K = ", k[i - 1], 4, ++y, 1);
    for (int m = 1; m <= k[i - 1]; ++m)
    {
      ++y;
      ++cnt;
      DataN(num[cnt], 8, y, 1);
      for (int j = 0; j < n; ++j)
        DataR(a[num[cnt]][j], Center(j + 1, n, 4, 2), y, 4);
    }
  }
}

После добавления в файл PT4MPIDemo функции MPIDemo4 необходимо изменить на 4 значение предпоследнего параметра функции CreateGroup и откорректировать функцию InitTask, добавив новый вариант в оператор switch:

    case 4:
      MPIDemo4();
      break;

Приведем решение данного задания, не указывая ту часть функции Solve, которая создается при генерации проекта-заготовки:

int n;
double b[10], c;
if (rank == 0)
{
  pt >> n;
  for (int i = 0; i < n; ++i)
    pt >> b[i];
}
MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast(b, n, MPI_DOUBLE, 0, MPI_COMM_WORLD);
if (rank != 0)
{
  int k;
  pt >> k;
  for (int k0 = 0; k0 < k; ++k0)
  {
    int m;
    pt >> m;
    c = 0;
    for (int i = 0; i < n; ++i)
    {
      double a;
      pt >> a;
      c += b[i]*a;
    }
    MPI_Send(&c, 1, MPI_DOUBLE, 0, m, MPI_COMM_WORLD);
  }
}
else
{
  for (int i = 0; i < n; ++i)
  {
    MPI_Status s;
    MPI_Recv(&c, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG,
      MPI_COMM_WORLD, &s);
    b[s.MPI_TAG] = c;
  }
  for (int i = 0; i < n; ++i)
    pt << b[i];
}

В этом решении используется как групповая пересылка данных (из процесса 0 в подчиненные процессы с применением функции MPI_Bcast), так и пересылка данных между двумя процессами (реализуемая функциями MPI_Send и MPI_Recv).

Следует обратить внимание на две особенности использования функций MPI_Send и MPI_Recv: во-первых, при пересылке найденного элемента результирующего вектора связанная с этим элементом информация (порядковый номер элемента) пересылается в виде параметра msgtag (и поэтому в принимающей функции MPI_Recv в качестве этого параметра указана константа MPI_ANY_TAG); во-вторых, принимающий процесс ранга 0 «не знает», сколько сообщений будет ему послано каждым из подчиненных процессов; ему известно лишь общее число посланных ему сообщений (равное размеру результирующего вектора), поэтому в принимающей функции он использует константу MPI_ANY_SOURCE в качестве «заменителя» ранга процесса-получателя.

Заметим, что задание MPIDemo4 представляет собой центральный фрагмент самопланирующего алгоритма умножения матрицы на вектор, описанного в разделе «Отладка параллельных программ». Исключен лишь «недетерминированный» этап, связанный с пересылкой матричных строк от главного процесса очередным подчиненным процессам; вместо этого матричные строки сразу распределяются задачником между подчиненными процессами, в которых они и должны вводиться.

Завершая описание процесса разработки новой группы заданий, отметим, что в задачнике предусмотрена возможность генерации html-страницы с текстом всех формулировок любой группы заданий. Чтобы получить такую страницу для разработанной нами группы, достаточно внести небольшое изменение в настройку «Command Arguments» нашего проекта, дополнив параметр –g символом # (в результате настройка «Command Arguments» примет вид -gMPIDemo# -n999). Теперь при запуске проекта PT4MPIDemo на экране вместо окна задачника с последним заданием из группы MPIDemo будет отображаться html-браузер с описанием созданной группы:

Примечание. Для генерации html-описания группы заданий с помощью программного модуля PT4Demo не обязательно использовать параметры командной строки. Достаточно запустить программу PT4Demo.exe с помощью ярлыка Demo.lnk, содержащегося в рабочем каталоге, выбрать из списка групп нужную группу и нажать клавишу F2 или кнопку в окне данной программы. Вывести html-описание группы можно также с помощью программы-заготовки, созданной для выполнения задания. Для этого достаточно изменить параметр в функции Task, удалив в нем номер и добавив символ #, например Task("MPIDemo#"). Заметим, что если указать в параметре символ #, не удаляя номер задания (например Task("MPIDemo4#")), то в html-описание будет включено только задание с указанным номером.


Prev

 

Рейтинг@Mail.ru

Разработка сайта:
М. Э. Абрамян, В. Н. Брагилевский

Последнее обновление:
01.01.2024