Программная поддержка модели BSF
Автор: Ежова Надежда Александровна, Соколинский Леонид Борисович
Статья в выпуске: 4 т.8, 2019 года.
Бесплатный доступ
В статье описана программная поддержка модели параллельных вычислений BSF (Bulk Synchronous Farm), ориентированной на итерационные алгоритмы с высокой вычислительной сложностью, разрабатываемые для многопроцессорных систем с распределенной памятью экзафлопсного уровня производительности. Программная поддержка включает в себя параллельный BSF-каркас и веб-приложение BSF-Studio. Приведены определение и классификация параллельных программных каркасов. Описан новый BSF-каркас, разработанный согласно BSF-модели: его файловая структура и логика работы. BSF-каркас представляет собой совокупность файлов исходного кода на языке C++, используемых для быстрого создания BSF-программ. Приведено подробное описание проектирования и реализации веб-приложения BSF-Studio, которое представляет собой визуальный конструктор BSF-программ. BSF-Studio обеспечивает поэтапное заполнение проблемно-зависимых частей BSF-каркаса, а также компиляцию и запуск программного кода.
Модель параллельных вычислений, параллельный каркас, веб-приложение
Короткий адрес: https://sciup.org/147233210
IDR: 147233210 | DOI: 10.14529/cmse190406
Текст научной статьи Программная поддержка модели BSF
Полноценная модель параллельных вычислений [1] должна включать в себя спецификационный компонент определяющий, что есть корректная программа в контексте данной модели. Параллельная программа может выдавать правильный результат, однако быть некорректной по отношению к требованиям модели параллельных вычислений. Такого рода некорректность приводит к тому, что применение стоимостных метрик используемой модели будет выдавать неверные оценки. Для предотвращения таких ситуаций и для ускорения создания программных кодов на практике часто используют параллельные каркасы, разрабатываемые на основе требований соответствующей модели параллельных вычислений.
Параллельный каркас (parallel skeleton) в общем виде представляет собой программную конструкцию (или библиотеку функций), которая инкапсулирует некоторый шаблон параллельных вычислений и межпроцессорных коммуникаций [2]. В идеальном случае параллельный каркас представляет собой компилируемый, но не исполняемый код. Чтобы использовать параллельный каркас, программист должен добавить проблемно-зависимые типы и структуры данных, а также реализовать проблемно-зависимые функции. При этом дополнения программного кода могут делаться инкрементально с сохранением свойства компилируемости. Каркас берет на себя функцию объединения проблемно-зависимых операций с программным кодом, обеспечивающим их параллельное выполнение и коммуникации. Таким образом, абстрагирование от аспектов, связанных с параллелизмом, может значительно упростить и систематизировать разработку параллельных программ и помочь в эффективном использовании стоимостных метрик, преобразовании и оптимизации исходного кода. В силу высокого уровня абстракции параллельные каркасы имеют концептуальную общность с функциями высшего порядка, применяемыми в функциональном программировании, а также с шаблонами объектно-ориентированного программирования, и многие конкретные каркасы используют эти механизмы.
В традиционных инструментах параллельного программирования используется подход, в соответствии с которым программист должен реализовывать низкоуровневое управление взаимодействием между параллельными процессами, используя механизмы, соответствующие используемой модели. Например, ядро MPI основано на обмене данными между процессами типа точка-точка с различными режимами синхронизации. Подобно этому, библиотеки потоковой обработки основаны на примитивах блокировки, условной синхронизации, атомарности и так далее. Этот подход является очень гибким, позволяя организовать взаимодействия любой сложности. Однако на практике многие параллельные алгоритмы основываются на хорошо понятных и хорошо известных шаблонах. Например, алгоритмы обработки изображений часто используют конвейерный параллелизм. Аналогичным образом, алгоритм для подбора параметра требует планирования параллельных процессов, которое не зависит от параметризованной задачи и диапазона значений исследуемого параметра.
Термин параллельный каркас (algorithmic skeleton) был введен Мюрреем Коулом (Murray Cole) в работе [3]. Появление этого термина явилось результатом осознания того факта, что многие параллельные приложения используют одни и те же внутренние коммуникационные шаблоны. Каркасный подход предполагает, что подобные шаблоны должны быть обобщены в виде программной конструкции или библиотеки функций, которые отвечают за реализацию скрытого управляющего «каркаса», оставляя программисту реализацию проблемно-зависимых функций, обеспечивающих решение конкретной задачи. Например, конвейерный каркас потребует от программиста реализацию операций, выполняемых на каждом шаге конвейера, в то время как каркас будет отвечать за распределение конвейерных операций между различными процессорами, межпроцессорные коммуникации, непрерывность работы конвейера и так далее. Аналогичным образом, в каркасе для подбора параметра программист должен будет предоставить код для одиночного прогона параметризованной задачи и требуемый диапазон значений параметра. Каркас будет определять (и, возможно, динамически корректировать) количество используемых рабочих, гранулярность распределенных вычислительных заданий, коммуникационные механизмы и отказоустойчивость. На более высоком уровне абстракции использование каркаса «разделяй-и-властвуй» может потребовать от программиста описания способов разделения задачи на независимые подзадачи и последующего объединения результатов их работы, решения вопроса, является ли исходная задача делимой соответствующим образом, и реализации алгоритмов решения неделимых подзадач. Каркас в этом случае будет отвечать за все остальное, начиная с вопроса, стоит ли вообще использовать параллелизм, и кончая такими деталями, как динамическая диспетчеризация, гранулярность и коммуникации.
Подобный высокоуровневый подход меняет процесс разработки программы в нескольких отношениях. Во-первых, он освобождает пользователя от нетривиальной задачи принятия правильных проектных решений на основе многочисленных, взаимосвязанных низкоуровневых особенностей конкретного приложения и конкретной вычислительной плат- формы. Во-вторых, использование отлаженного и апробированного параллельного каркаса существенно уменьшает вероятность появления ошибок в результирующем коде, что особенно важно для сложных задач, решаемых на вычислительных системах с массовым параллелизмом, где традиционная отладка является слишком сложной. В-третьих, использование параллельного каркаса дает гарантию параллельной эффективности вместо апостериорной оценки производительности, в результате которой, возможно, придется отказаться от трудоемкой параллельной программы по причине ее фатальной неэффективности. В-четвертых, каркасы предоставляют семантически обоснованные методы для сборки и оптимизации программного кода, открывающие новые перспективы в разработке программного обеспечения (в том числе, в повторном использовании программных кодов). И последнее, но не менее важное: повышение уровня абстракции, заключающееся в переходя от частного к общему, дает новое понимание фундаментальных принципов параллельного программирования [2].
В зависимости от функциональности параллельные каркасы могут быть разделены на следующие три типа [4]:
-
- каркасы с параллелизмом данных (data-parallel), применяемые для выполнения одинаковых операций над элементами больших однородных структур данных;
-
- каркасы с параллелизмом задач (task-parallel), оперирующие задачами и обеспечивающие их синхронизацию в соответствии со связями по данным;
-
- революционные (resolution) каркасы, определяющие общий метод распараллеливания для семейства схожих задач.
Таксономия базовых параллельных каркасов приведена в табл. 1.
-
- Мар является наиболее важным базовым каркасом с параллелизмом данных. Его природа тесно связана с функциональными языками программирования. Семантика каркаса тар заключается в том, что некоторая функция может быть одновременно применена ко всем элементам списка в целях распараллеливания вычислений. Параллелизм данных организуется следующим образом: список разбивается на подсписки равной длины по числу процессорных узлов, и каждый процессорный узел обрабатывает свой подсписок параллельно с другими узлами. Обменов данными между процессорными уздами при этом не происходит. По завершению обработки полученные результаты объединяются в общий результат. Таким образом, каркас тар соответствует схеме параллельной обработки SIMD (single instruction, multiple data) [5].
Таблица 1
Таксономия базовых параллельных каркасов
Тип каркаса |
Область применения |
Примеры элементарных каркасов |
Параллелизм данных |
Структуры данных |
map, fork, reduce |
Параллелизм задач |
Задачи |
farm, pipe |
Fork работает подобно тар. Разница в том, что вместо применения одной и той же функции ко всем элементам списка, к каждому элементу применяется своя функция. Таким образом, каркас fork соответствует схеме параллельной обработки MIMD (multiple instruction, multiple data) [5].
Reduce используется для вычисления инфиксных операций над списком путем деления элементов списка на пары и применения к каждой паре указанной инфиксной операции. Полученные результаты образуют список в два раза меньшей длины, к которому снова применяется reduce, и так да тех пор, пока не будет получено итоговое интегральное значение. Таким образом, в отличие от тар, каркас reduce требует обменов данными между процессорными узлами для агрегирования частичных результатов.
Farm реализует параллельное выполнение задач по схеме мастер-рабочие. Этот каркас также называют bag of tasks (пакет задач). Farm обеспечивает параллельное выполнение независимых подзадач на узлах-рабочих и слияние полученных ими результатов в общий результат на узле-мастере, который финализирует решение задачи.
Pipe реализует конвейерный параллелизм. Этот каркас целесообразно использовать, когда одна и та же задача должна решаться для многих наборов входных данных. В этом случае решение задачи разбивается на последовательные стадии, количество которых называется длиной конвейера. Каждая стадия выполняется отдельным процессорным узлом. Параллелизм достигается за счет того, что одновременно с выполнением г-той стадии для набора данных Д-h выполняется стадия (? — 1) для набора т^+1Ф Каркас pipe обеспечивает синхронизацию выполнения стадий и передачу промежуточных результатов от одной стадии другой. Заметим, что в общем случае достаточно иметь каркас pipe с фиксированной длиной, так как каркасы могут вкладываться друг в друга.
Статья имеет следующую структуру. В разделе 1 описывается структура параллельного BSF-каркаса на языке C++, используемого для быстрого создания BSF-программ. В разделе 2 описывается визуальный конструктор BSF-программ.
-
1. Параллельный каркас для модели BSF
Модель параллельных вычислений BSF (Bulk Synchronous Farm) была предложена в работах [6, 7]. Модель BSF является расширением модели BSP и ориентирована на итерационные алгоритмы с высокой вычислительной сложностью, разрабатываемые для многопроцессорных систем с распределенной памятью экзафлопсного уровня производительности. Отличительной особенностью модели BSF от других известных моделей параллельных вычислений является возможность оценки границы масштабируемости алгоритма на ранних этапах его разработки.
В данном разделе описывается параллельный BSF-каркас на языке Сч—Р, предназначенный для быстрого создания параллельных BSF-программ для кластерных вычислительных систем. Для организации обменов сообщениями между процессорными узлами BSF-каркас использует программный интерфейс MPI [8-10]. BSF-каркас спроектирован таким образом, что он допускает поэтапное заполнение проблемно-зависимых частей, и при этом компилируется на всех этапах разработки. Исходные тексты BSF-каркаса свободно доступны в сети Интернет по адресу
-
1.1. Файловая структура каркаса
BSF-каркас состоит из двух групп файлов:
-
1) файлы с префиксом «BSF» содержат проблемно-независимый код и не подлежат изменениям со стороны пользователя;
-
2) файлы с префиксом «Problem» предназначены для заполнения пользователем проблемно-зависимых частей программы.
Описания всех файлов исходного кода приведена! в табл. 2. Граф зависимостей файлов BSF-каркаса по включению с помощью директивы ^include приведен на рис. 1. Серым закрашены файлы, в которых пользовательские изменения не допускаются; узорной штриховкой обозначены файлы с предопределенными заголовками определений, тело которых должен заполнить пользователь; белое заполнение соответствует файлам, полностью заполняемым пользователем. Порядок заполнения BSF-каркаса приведен в файле ReadMe, доступном по адресу
Таблица 2
Файлы исходного кода BSF-каркаса
Файл |
Описание |
Проблемно-независимый код |
|
BSF-Code.cpp |
Реализации головной функции main и всех проблемно-независимых функций |
BSF-Data.h |
Проблемно-независимые переменные и структуры данных |
BSF-Forwards.h |
Предописания проблемно-независимых функций |
BSF-Include.h |
Включение проблемно-независимых библиотек (iostream, mpi.h, omp.h) |
BSF-ProblemFunctions.h |
Предописания предопределенных функций с проблемнозависимой реализацией |
BSF-Types.h |
Определения проблемно-независимых типов |
Проблемно-зависимый код |
|
Problem-bsfCode.cpp |
Реализация предопределенных проблемно-зависимых функций |
Problem-bsfParameters.h |
Предопределенные проблемно-зависимые параметры |
Problem-bsfTypes. h |
Предопределенные проблемно-зависимые типы |
Problem-Data, h |
Проблемно-зависимые переменные и структуры данных |
Problem-Forwards, h |
Предописания проблемно-зависимых функций |
Problem-Include.h |
Включение проблемно-зависимых библиотек |
Problem-P arameters. h |
Проблемно-зависимые параметры |
Problem-Types, h |
Проблемно-зависимые типы |

Рис. 1. Граф зависимостей файлов BSF-каркаса по включению ^include
-
- PP_BSF_ITER_OUTPUT: если указанный ^define определен, то будет выполняться вывод результатов итераций с определенным шагом, задаваемым константой PP_BSF_TRACE_COUNT;
-
- PP_BSF_TRACE_COUNT: задает частоту вывода результатов итераций, например, если указано значение 10, то будет выводиться результат каждой десятой итерации (если определен ^define PP_BSF_ITER_OUTPUT);
-
- PP_BSF_OMP: если указанный ^define определен, то будет включена #pragma omp parallel for перед циклом, реализующим функцию Мар;
PP_BSF_NUM_THREADS: указывает количество потоков управления, в директиве ^pragma omp parallel (если константа PP_BSF_NUM_THREADS не определена, то запускается количество потоков, определенное по умолчанию).
Файл Problem-bsf Types.h включает в себя заголовки описаний (описания с пустым телом) трех предопределенных структурных типов:
-
- PT_bsf_data_T: описывает структуру данных, передаваемых каждому рабочему для выполнения очередной итерации (может содержать текущее приближение и другие параметры);
-
- PT_bsf_mapElem_T: описывает структуру элемента списка «Мар»;
-
- PT _bsf_reduceElem_ Т: описывает структуру элемента списка «Reduce».
Файл BSF-ProblemFunctions.h включает в себя предописания (форварды) следующих предопределенных пользовательских функций, которые должны быть реализованы в файле Problem-bsfCode, срр:
-
- PC_bsf_AssignListSize(int* listSize): присваивает переменной *listSize длину списка «Мар» (совпадает с длиной списка «Reduce»);
-
- PC_bsf_CopyData(PT_bsf_data_T* dataln, PT_bsf_data_T* dataOut): копирует данные из структура! *dataOut в структуру *dataln;
-
- PC_bsf_IterOutput(PT_bsf_reduceElem_T* reduceResult, int count, PT_bsf_data_T data, int iterCount, double elapsedTime): выводит результаты итерации, используя в качестве параметров reduceResult — результат выполнения функции Reduce над всем списком; count — количество элементов, участвовавших в Reduce; data — задание, выполненное на данной итерации; iterCount — количество выполненных итераций; elapsedTime — общее время, затраченное на решение задачи.
-
- PC_bsf_Init(bool* success): выделяет память для проблемно-зависимых структур данных и выполняет их инициализацию; если необходимую память выделить не удалось, переменной *success должно быть присвоено значение false;
-
- PC_bsf_MapF(PT_bsf_mapElem_T* mapElem, PT_bsf_reduceElem_T* reduceElem, PT_bsf_data_T* data, int* success): вычисляет функцию F, являющуюся параметром оператора Мар, используя аргументы *mapElem — элемент списка «Мар», над которым выполняется функция F; *reduceElem — элемент списка «Reduce», которому должно быть присвоено значение функции F; *data — задание, содержащее текущее приближение; параметру *success должно быть присвоено значение 0, если полученный элемент списка «Reduce» должен игнорироваться при выполнении операции Reduce.
-
- PC_bsf_ParametersOutput(int numOfWorkers, PT_bsf_data_T data): выводит начальные параметры задачи, используя аргументы numOfWorkers — количество процессов-рабочих и data — начальное задание, содержащее начальное приближение;
-
- PC_bsf_ProblemOutput(PT_bsf_reduceElem_T* reduceResult, int count, PT_bsf_data_T data, int iterCount, double t): выводит конечные результаты работы программы, используя аргументы *reduceResult — результат выполнения оператора Reduce, count количество элементов «суммированных» при выполнении оператора Reduce, data — последнее задание (последнее приближение), t — общее время работы программы;
-
- PC_bsf_ProcessResults(bool* exit, PT_bsf_reduceElem_T* reduceResult, int count, PT_bsf_data_T* data): обрабатывает результаты, полученные в результате выполнения очередной итерации, используя аргументы *reduceResult — результат выполнения оператора Reduce, count количество элементов «суммированных» при выполнении оператора Reduce, data — последнее задание (последнее приближение); если вычисления необходимо продолжить, переменной *exit присваивается значение true, в противном случае — false;
-
- PC_bsf_ReduceF(PT_bsf_reduceElem_T* х, PT_bsf_reduceElem_T* у, PT_bsf_reduceElem_T* z): реализует функцию, являющуюся аргументом оператора Reduce, по формуле z := х ф у.
-
- PC_bsf_SetInitApproximation(PT_bsf_data_T* data): записывает в *data начальное задание (начальное приближение);
-
- PC_bsf_SetMapSubList(PT_bsf_mapElem_T* subList, int count, int offset, bool* success): заполняет подсписок списка «Мар», используя аргументы *subList — указа-
- тель на первый элемент подсписка, count — длина подсписка, offset — сдвиг, относительно начала списка; если у элемента списка «Мар» имеются поля типа указатель, то необходимо выделить память под вектор, матрицу или другую структуру; если обнаружена нехватка памяти, переменной *success необходимо присвоить значение false.
Файл BSF-Types.h содержит определения двух проблемно-независимых структурных типов: BTorderT и ВТ_ extendedReduceElem_ Т. Структурный тип BTorderT определяет структуру задания, пересылаемого мастером рабочим, и включает в себя два поля: data типа PT_bsf_data_ Т, определяемого в файле Problem-bsfTypes.h (см. стр. 89), и exit типа char; Если поле exit содержит двоичное значение 1, то процессы-рабочие должны завершить свою работу. Структурный тип ВТ_ extendedReduceElem_ Т определяет структуру элемента расширенного списка «Reduce» и включает в себя два поля: elem типа РТ_ bsf_ reduceElem_ Т, определяемого в файле Problem-bsfTypes.h (см. выше), и counter типа int. До выполнения оператора Reduce в поле counter может находиться значение 1, либо 0. Значение 0 означает, что данный элемент должен быть проигнорирован при выполнении оператора Reduce. Значение 1 означает, что данный элемент должен быть учтен при выполнении оператора Reduce. После выполнения оператора Reduce над списком (подсписком) в поле counter результирующего элемента заносится количество учтенных исходных элементов.
Файл BSF-Data.h содержит определения проблемно-независимых переменных и структур данных. В число переменных входят следующие:
-
- BDlistSize: длина списка «Мар» (совпадает с длиной списка «Reduce»);
-
- BD size: количество MPI-процессов;
-
- BDrank: номер текущего МР1-процесса;
-
- BDmasterRank: номер процесса-мастера (равен BD_size — 1);
-
- BD numOfWorkers: количество процессов-рабочих (равно BD_size — 1);
-
- BD elemsPerWorker: количество элементов списка, приходящихся на одного рабочего (равно VBDJistSize / В D_numO/Workers);
-
- BDtailLength: длина остатка списка после деления на число рабочих (равно BDJistSize — В D_elems PerWorker • BD_numOf Workers)-,
-
- BDexit: индикатор завершения вычислений;
-
- BD_success: индикатор успешного выполнения инициализации;
-
- BD t: время, затраченное на вычисления (не учитываются ввод/вывод данных и инициализация);
-
- BD iterCount: количество выполненных итераций.
В число структур данных входят следующие:
-
- BD data; структура, в которой формируется данные, необходимые рабочим для выполнения очередной итерации;
-
- BD mapSubList: указатель на подсписок, обрабатываемый рабочим (используется только процессами-рабочими);
-
- BD_extendedReduceList: указатель на расширенный список «Reduce»;
-
- BD_extendedReduceResult_P: указатель на структуру, в которой вычисляется результирующий элемент при выполнении оператора Reduce над расширенным списком «Reduce»;
-
- BD order: указатель на структуру, в которой формируется задание для рабочего;
-
- BDstatus: указатель на массив системных MPI-структур «MPI_Status», содержащих параметры сообщений для каждого рабочего;
-
- BDrequest: указатель на массив системных MPI-переменных, содержащий идентификаторы асинхронных обменов по числу процессов-рабочих (используется только процессом-мастером);
-
- BDsubListSize: указатель на массив целых чисел, содержащий размеры подсписков для всех рабочих;
-
- BD_offset: указатель на массив целых чисел, содержащий для каждого рабочего сдвиг его подсписка относительно начала общего списка.
-
- BCMpiRun выполняет инициализацию MPI и соответствующих переменных;
-
- BCInit выделяет необходимую память для структур данных, инициализирует переменные и заполняет список «Мар» исходными данными;
-
- ВС_Master реализует процесс, выполняемый на узле-мастере;
-
- BC Worker реализует процесс, выполняемый на узлах-рабочих;
-
- BC_MasterMap реализует шаг «Мар» на узле-мастере;
-
- BC_MasterReduce реализует шаг «Reduce» на узле-мастере;
-
- BC MpiRun осуществляет инициализацию MPI;
-
- BC_WorkerMap реализует шаг «Мар» на узлах-рабочих;
-
- BC WorkerReduce реализует шаг «Reduce» на узлах-рабочих;
-
- BC_ProcessExtendedReduceList обрабатывает указанную часть списка «Reduce».
Файл BSF-Include.h подключает с помощью директивы ^include необходимые системные библиотеки. Файл BSF-Forwards.h содержит предописания функций, реализуемых в файле BSF-Code.cpp. Файл Problem-bsfCode.cpp содержит реализации проблемно-зависимых функций, предописания которых находятся в файле BSF-ProblemFunctions.h (см. стр. 89). Файл Problem-Include.h подключает с помощью директивы ^include системные библиотеки, необходимые для реализаций проблемно-зависимых функций. Файл Problem-Forwards.h содержит предописания пользовательских функций, используемых для реализации предопределенных проблемно-зависимых функций. Файл Problem-Types.h содержит определения пользовательских типов данных. Файл BSF-Types Problem-Data. h, содержит определения пользовательских глобальных переменных и структур данных. Файл Problem-Parameters.h содержит определения проблемно-зависимых констант.
-
1.2. Логика работы каркаса
Кратко опишем общую логику работы каркаса. Головная функция main вызывает функцию BC MpiRun, которая выполняет инициализацию MPI и определяет значения переменных:
-
- BDsize: количество запущенных MPI-процессов (не может быть меньше двух);
-
- BD rank: номер собственного МР1-процесса.
Затем вызывается функция BC Init, которая выделяет необходимую память и определяет значения проблемно-независимых глобальных переменных и структур данных, определенных в файле BSF-Data.h (см. стр. 91). Если хотя бы в одном MPI-процессе не удалось выделить необходимую память, процесс-мастер выводит в глобальный поток cout диагностическое сообщение «Error: PCbsflnit failed (not enough memory)!», и все MPI-процессы заканчивают свою работу. Если выделение памяти и инициализация прошли успешно, то процесс с номером BDmasterRank выполняет функцию BCMaster, а остальные процессы выполняют функцию ВС_ Worker.
Функция ВС Master реализует алгоритм работы мастера. Сначала выводятся параметры задачи с помощью функции PCbsfParametersOutput. Затем организуется основной итерационный цикл. В ходе каждой итерации выполняются следующие действия: 1) вызывается функция BCMasterMap, пересылающая всем рабочим в асинхронном ре жиме (с помощью функции MPIIsend) задание для очередной итерации;
-
2) вызывается функция ВС MasterReduce, получающая от всех рабочих в асинхронном режиме (с помощью функции MPIIrecv) результат выполнения оператора Reduce на подсписках;
-
3) вызывается предопределенная проблемно-зависимая функция РС_ bsf ProcessResults, проверяющая критерий завершения, вычисляющая очередное приближение и формирующая задание для следующей итерации;
-
4) если определен ((define PPBSFITEROUTPUT, выводятся результаты итерации.
Основной итерационный цикл завершается, когда в переменную BD exit функция PC bsf ProcessResults поместит значение false. В этом случае выполняется функция BC MasterMap с параметром false, приводящая к завершению процессов-рабочих. После этого происходит вывод результатов с помощью предопределенной проблемно-зависимой функции РС_ bsf ProblemOutput, после чего функция ВС Master завершает свою работу.
Функция ВС_ Worker, реализует алгоритм работы рабочего, выполняя в цикле последовательно две функции: ВС_ WorkerMap и ВС_ WorkerReduce. Выход из этого цикла происходит, когда функция ВС_ WorkerMap вернет значение false. Функция ВС_ WorkerMap выполняет следующие действия:
-
1) в синхронном режиме (с помощью функции MPIRecv) получает от мастера задание для выполнения очередной итерации;
-
2) если поле exit в полученном задании содержит значение true, происходит выход из функции;
-
3) применяет предопределенную проблемно-зависимую функцию PC_bsf_MapF ко всем элементам своего подсписка «Мар», помещая результаты в расширенный список «Reduce».
Функция ВС_ WorkerReduce выполняет следующие действия:
-
1) выполняет оператор Reduce для своей части расширенного списка «Reduce» с помощью функции ВС ProcessExtendedReduceList, которая помещает результат в структуру с указателем extendedReduceResultP,
-
2) пересылает в синхронном режиме (с помощью функции MPI_Send) полученный результат мастеру.
-
2. Визуальный конструктор BSF-программ
В данном разделе описывается процесс проектирования и реализации программной системы BSF-Studio для быстрого создания корректных BSF-программ на языке C++ с использованием описанного в разделе 1 параллельного каркаса. Основными функциональными возможностями BSF-Studio являются следующие:
-
- пошаговое заполнение проблемно-зависимых частей BSF-каркаса;
-
- инкрементная (после каждого шага) компиляция программного кода в облачной среде, поддерживающей технологии параллельного программирования ОрепМР [11] и MPI [9], с выводом диагностических сообщений компилятора;
-
- запуск скомпилированного исполняемого кода в облачной среде, поддерживающей технологии параллелвного программирования ОрепМР и MPI, с выводом резулвтатов выполнения;
-
- загрузка отлаженного исходного кода.
-
2.1. Проектирование и реализация клиентской части
Программная система BSF-Studio представляет собой веб-приложение с клиент-серверной архитектурой. Клиентская часть представляет собой одностраничное приложение (Single Page Application) [12], серверная частв — Docker-контейнер [13], в котором запускается приложение, выполняющее по запросу компиляцию и запуск программы, и возвращающее резулвтат. Связь между клиентом и севером осуществляется посредством REST API [14].
Интерфейс клиента BSF-Studio представляет собой мастер (wizard) для заполнения BSF-каркаса. Процесс работы мастера разбивается на следующие шаги:
-
1) определение константных параметров модели BSF и параметров задачи (размерность, число уравнений и др.);
-
2) определение типов данных текущего приближения и элементов списков «Мар» и «Reduce»;
-
3) определение пользовательских переменных и структур данных;
-
4) реализация пользовательских функций.
-
2.2. Проектирование и реализация серверной части
Указанные шаги реализуются путем последовательного заполнения HTML-форм, содержащих текстовые поля для заполнения соответствующих фрагментов программного кода.
Реализация интерфейса выполнена с помощью JavaScript-библиотеки React с открытым исходным кодом [15]. React позволяет создавать интерактивные пользовательские интерфейсы на основе компонентного подхода: фрагменты веб-приложения (компоненты) инкапсулируются и используются независимо друг от друга. А за счет большого количества библиотек с открытым исходным кодом, предоставляющих готовые React-компоненты, разработка приложений сводится к подбору, подключению и использованию сторонних модулей.
Для работы с данными использовалась библиотека Redux [15]. Redux берет на себя функции записи, хранения, организации доступа к данным из любого React-компонента. Для реализации HTML-форм использовалась библиотека Redux Form [16]. Redux Form предоставляет готовые компоненты и программную обвязку для реализации HTML-форм и хранения их данных с использованием Redux. Для реализации текстовых полей, предназначенных для работы с программным кодом, использовалась библиотека React Асе [17]. Она предоставляет компонент, в котором реализовано оформление текста в зависимости от языка программирования, выбранного из предоставляемого списка, нумерация строк, настраиваемые панели инструментов.
По нажатию на кнопки «Compile» и «Run» программный код компонуется в файлы и отправляется посредством HTTP-запроса на сервер, где происходит объединение пользовательских файлов и файлов BSF-каркаса и выполняется их компиляция и/или запуск.
Результаты компиляции и запуска отображаются в интерфейсе веб-приложения. По нажатию на кнопку «Download» формируются и загружаются файлы BSF-каркаса.
Для разработки серверной части приложения BSF-Studio использована технология Docker [18], которая обеспечивает возможность развертывания приложения на базе контейнерной виртуализированной среды. Это позволило создать виртуальное окружение, максимально приближенное к окружению кластерной вычислительной системы. Данный подход позволяет развернуть серверную часть приложения BSF-Studio в рамках любой операционной системы и аппаратного обеспечения, а также упростить процесс развертывания.
Docker предполагает написание сценария, который содержит последовательные инструкции по настройке окружения. В результате выполнения этого сценария строится виртуальный образ, эмулирующий описанное окружение. В рамках этого Docker-сценария необходимо описать на основе какой операционной системы будет функционировать образ, какие подсистемы и библиотеки должны быть установлены, а также произведена настройка и подготовка образа к функционированию приложения внутри него.
Для решения поставленных задач было взято наиболее компактное ядро Linux Alpine [18], содержащее минимальный необходимый набор программ, который позволяет операционной системе функционировать. В качестве компилятора был выбран GNU С Compiler (GCC) [19]. Помимо этого, были установлены библиотеки параллельного программирования ОрепМР и MPICH [20]. Для проверки корректности установки и функционирования всех необходимых элементов при сборке образа производится тестовая компиляция нескольких проектов.
Далее необходимо выбрать технологии и средства реализации непосредственно серверного приложения, которое будет осуществлять сборку предоставленного пользователем исходного кода и возвращать результат компиляции посредством REST API.
-
- Express-request-id — модуль для Express, который позволяет получать параметры из строки запроса [22].
-
- ShellJS — библиотека, которая позволяет вызывать команды операционной системы [23].
-
- Multer — библиотека, которая позволяет фреймворку Express принимать и отправлять файлы [24].
Для автоматизации развертывания контейнеризованных приложений клиентской и серверной частей BSF-Studio создан кластер на основе системы Kubernetes [17].
BSF-Studio-API работает согласно следующему алгоритму. Импортируются необходимые библиотеки и модули: Express, express-request-id, ShellJS, Multer. Далее формируются пути до рабочих директорий для текущего запроса и для приложения. Происходит инициализация экземпляра веб-сервера Express и хранилища, в котором будут находиться полученные в текущем запросе файлы и их именование, и инициализация экземпляра Multer. Модуль Express-Request-Id подключается к веб-серверу Express и происходит его запуск. Далее веб-сервер ожидает HTTP-запрос на компиляцию и запуск программного кода. Такой запрос должен содержать файлы проблемно-зависимой части кода для BSF-каркаса в качестве параметров. При получении запроса происходит его обработка и загрузка присланных файлов и файлов каркаса в рабочую директорию. Далее выполняется компиляция, результаты отправляются клиенту в формате JSON, рабочая директория удаляется.
Исходные тексты BSF-Studio свободно доступны в сети Интернет:
-
- серверная часть: https://github.com/ezhova-nadezhda/BSF-Studio-API ;
-
- клиентская часть: https://github.com/ezhova-nadezhda/BSF-Studio .
Заключение
В статье была описана программная поддержка модели BSF. Она включает в себя параллельный BSF-каркас и веб-приложение BSF-Studio. Параллельный BSF-каркас представляет собой совокупность файлов исходного кода на языке C++, используемых для быстрого создания BSF-программ. BSF-каркас содержит реализацию всех проблемнонезависимых функций, организующих параллельное выполнение программы с использованием библиотек MPI и ОрепМР. Он также содержит определения (заголовки) проблемно-зависимых функций, реализуемых пользователем. Отличительной особенностью BSF-каркаса является то, что он предполагает поэтапное заполнение пользовательской части исходных кодов и при этом компилируется на всех этапах. Веб-приложение BSF-Studio представляет собой визуальный конструктор BSF-программ. BSF-Studio обеспечивает поэтапное заполнение проблемно-зависимых частей программного кода, компиляцию и запуск приложения в облачной среде.
Исследование выполнено при финансовой поддержке РФФИ в рамках научного проекта № 17-07-00352 а, Правительства РФ в соответствии с Постановлением №211 от 16.03.2013 г. (соглашение № 02.АОЗ.21.0011) и Министерства образования и науки РФ (государственное задание 2.7905.2017/8.9).
Список литературы Программная поддержка модели BSF
- Ежова Н.А., Соколинский Л.Б. Обзор моделей параллельных вычислений // Вестник ЮУрГУ. Серия: Вычислительная математика и информатика. 2019. Т. 8, № 3. С. 58-91. DOI: 10.14529/cmse190304
- Leasure B. et al. Parallel Skeletons // Encyclopedia of Parallel Computing. Springer US, 2011. P. 1417-1422. DOI: 10.1007/978-0-387-09766-4_24
- Cole M.I. Algorithmic Skeletons: Structured Management of Parallel Computation. MIT Press, 1991.
- Gonzalez-Velez H., Leyton M. A Survey of Algorithmic Skeleton Frameworks: High-Level Structured Parallel Programming Enablers // Softw. Pract. Exp. John Wiley & Sons, Ltd. 2010. Vol. 40, no. 12. P. 1135-1160. DOI: 10.1002/spe.1026
- Dongarra J. et al. Sourcebook of Parallel Computing. Morgan Kaufmann, 2003. 842 p.
- Ежова Н.А., Соколинский Л.Б. BSF: модель параллельных вычислений для многопроцессорных систем с распределенной памятью // Вестник ЮУрГУ. Серия: Вычислительная математика и информатика. 2018. Т. 7, № 2. С. 32-49.
- DOI: 10.14529/cmse180204
- Ежова Н.А., Соколинский Л.Б. Исследование масштабируемости итерационных алгоритмов при суперкомпьютерном моделировании физических процессов // Вычислительные методы и программирование. 2018. Т. 19, № 4. С. 416-430.
- DOI: 10.26089/NumMet.v19r437
- Gropp W. et al. A High-Performance, Portable Implementation of the MPI Message Passing Interface Standard // Parallel Comput. 1996. Vol. 22, no. 6. P. 789-828.
- DOI: 10.1016/0167-8191(96)00024-5
- Gropp W. MPI3 and Beyond: Why MPI Is Successful and What Challenges It Faces // Recent Advances in the Message Passing Interface. EuroMPI 2012. Lecture Notes in Computer Science, Vol. 7490 / Ed. by J.L. Träff, S. Benkner, J.J. Dongarra. Springer, 2012. P. 1-9.
- DOI: 10.1007/978-3-642-33518-1_1
- Gropp W., Lusk E., Skjellum A. Using MPI: Portable Parallel Programming with the Message-Passing Interface. Second Edition. MIT Press, 1999. 395 p.
- Pas R. van der, Stotzer E., Terboven C. Using OpenMP - The Next Step: Affinity, Accelerators, Tasking, and SIMD (Scientific and Engineering Computation). 1st ed. MIT Press, 2017. 392 p.
- Patel Y. White Paper On Single Page Application. Knowarth. 2015. URL: https://www.knowarth.com/wp-content/uploads/2015/02/Single-Page-ApplicationWhite-Paper.pdf (дата обращения: 19.09.2019)
- Docker Open Source Engine Guide. SUSE Linux Enterprise Server 15 SP1. SUSE LLC. 2019. URL: https://documentation.suse.com/sles/15-SP1/pdf/book-sles-docker_color_ en.pdf (дата обращения: 19.09.2019)
- Allamaraju S. RESTful Web Services Cookbook: Solutions for Improving Scalability and Simplicity. 1st edition. Yahoo Press, 2010. 316 p.
- Banks A., Porcello E. Learning React Functional Web Development with React and Redux. O'Reilly Media, 2017. 350 p.
- Rasmussen E. Redux Form: The Best Way to Manage Your Form State in Redux. URL: https://redux-form.com/8.2.2/docs/api/ (дата обращения: 25.02.2018).
- Hrisho J. React-Ace (GitHub Repository). URL: https://github.com/securingsincity/react-ace (дата обращения: 25.02.2019).
- Matthias K., Kane S.P. Docker: Up & Running: Shipping Reliable Containers in Production. 2nd Edition. O'Reilly Media, 2018. 352 p.
- Rothwell T., Youngman J. The GNU C Reference Manual. Free Software Foundation, Inc, 2007. P. 86. URL: https://www.gnu.org/software/gnu-c-manual/gnu-c-manual.pdf (дата обращения: 20.02.2018).
- MPICH Documentation and Guides. URL: https://www.mpich.org/documentation/guides/ (дата обращения: 20.02.2018).
- Brown E. Web Development with Node and Express: Leveraging the JavaScript Stack. 1st ed. O'Reilly Media, 2014. 332 p.
- Strukchinsky V. Express-Request-Id (GitHub Repository). URL: https://github.com/floatdrop/express-request-id (дата обращения: 17.03.2018).
- Fischer N., Freitag B. ShellJS - Unix Shell Commands for Node.js (GitHub Repository). URL: https://github.com/shelljs/shelljs (дата обращения: 13.03.2018).
- Unnebäck L. Multer (GitHub Repository). URL: https://github.com/expressjs/multer (дата обращения: 17.03.2018).