Пример разработки приложения для гибридной параллельной вычислительной системы.

Приложение: решение задачи Дирихле для уравнения Пуассона на равномерной прямоугольной сетке.

Начнем с планирования структуры исходного текста.

Текст приложения делится на общую часть и реализацию конкретного численного метода на конкретном оборудовании. Общая часть (в нашем случае – главная программа) не должна, по возможности, зависеть ни от выбранного метода решения задачи (например, метода Якоби, метода верхней релаксации или какого-то другого), ни от вида оборудования, выбранного для реализации вычислительно-емкой части приложения. Реализация конкретного метода на конкретном оборудовании (набор вызываемых функций), напротив, зависит и от того, и от другого, но должна быть оформлена некоторым унифицированным образом. Например, набор функций, реализующий метод Якоби на SMP-узле, не должен, в идеале, отличаться по внешнему интерфейсу от набора функций, реализующего метод верхней релаксации на векторно-мультитредовом ускорителе.

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

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

  • выполнить очередную итерацию метода над той частью сетки, которая обрабатывается данным узлом,
  • обменяться границами с соседними узлами,
  • если процесс не сошелся, повторить.

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

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

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

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

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

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

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

В полной аналогии с функциями передачи данных между процессором и сопроцессором – ускорителем,  «на всякий случай» в функции выполнения итерации вводятся дополнительные аргументы: указатели на обрабатываемые данные в памяти ускорителя. Чисто программная реализация эти аргументы игнорирует.

Ниже приводится описание программного интерфейса общей части приложения.

Программный интерфейс вычислительного ядра «итерация численного метода решения задачи Дирихле для уравнения Пуассона».

Прототипы функций находятся в poissn.h.

Задача решается в прямоугольнике, на равномерной прямоугольной сетке.

Данные, используемые в расчете

  1. Массив p, nrows строк на ncols столбцов, поле величин, включающее края, изменяется в процессе расчета.
  2. Массив rhs, того же размера, поле правых частей, во время расчета не меняется, но используется.
  3. Массивы cop и corhs, копии массивов p и rhs, загруженные в сопроцессор.
  4. Коэффициенты delx и dely, характеризующие форму ячеек расчетной сетки.

  5. Дополнительные коэффициенты, используемые в конкретных методах: например,  только в методе красно-черной релаксации нужен коэффициент релаксации. Эти значения собраны в массив addcoeff.

Описание функций:

  1. extern void initval( float *p[], float *rhs[], int nrows, int ncols,
             float pborder, float pinside, float rhsborder, float rhsinside );

    Формирует в массивах p и rhs поле величин и поле правых частей, соответственно, поле величин равно pinside внутри области и pborder на границе, поле правых частей – rhsinside и rhsborder, соответственно. Функция не затрагивает сопроцессор и не является обязательной (нельзя рассчитывать, что ее обязательно вызовут).

  2. extern void allocate( int nrows, int ncols,
             float **pcop, float **corhs );

    Выделить в памяти сопроцессора место под поля величин и правых частей, размером nrows строк на ncols столбцов, и вернуть адреса выделенных областей в pcop и corhs, соответственно. Реализация итерации на сопроцессоре устроена таким образом, что ей нужны два поля величин, исходное и результирующее для данной итерации, причем на каждой итерации они меняются местами. Поэтому pcop – это массив из двух указателей, каждый из которых указывает на одно из двух полей величин одинакового размера. Возвращаемые значения формально объявлены в прототипе функции как указатели на float, но в действительности являются адресами в адресном пространстве сопроцессора (в программе на С использовать их как адреса нельзя, можно только передать в качестве параметров при работе с сопроцессором). К этим значениям можно прибавлять смещения, измеряемые в значениях типа float. Функция является обязательной, то есть до начала работы с сопроцессором она должна быть вызвана ровно один раз, и этим можно пользоваться для вставки в нее тех или иных подготовительных действий, касающихся как сопроцессорной, так и программной реализации вычислительного ядра.

  3. extern void upload( float *p[], float *rhs[], int nrows, int ncols,
             float *cop, float *corhs );

    Загрузить в память сопроцессора массивы p и rhs, размером nrows на ncols, по сопроцессорным адресам cop и corhs. Если один из первых двух аргументов указан равным нулю, загрузка соответствующего массива не происходит.

  4. extern void download( float *p[], float *rhs[], int nrows, int ncols,
             float *cop, float *corhs );

    Операция, обратная предыдущей.

  5. extern void poissn( float *p[], float *rhs[], float *pcop, int icop, float *corhs,
             int nrows, int ncols, float delx, float dely, float addcoeff[] );

    Выполнить одну итерацию численного метода решения задачи Дирихле. В зависимости от того,  реализует ли эта функция вычисления на процессоре или на сопроцессоре, используются соответствующие указатели на поля величин и правых частей: процессорные или же сопроцессорные. В случае сопроцессора в качестве исходного поля величин используется массив pcop[icop], в качестве результирующего – pcop[1-icop] (если исходный массив – нулевой, то результирующий – первый, и наоборот).

  6. Обзор текста реализаций метода Якоби на различном оборудовании.

    Тексты общей части приложения progrev_shmem.c и poissn.h в особых комментариях не нуждаются. Для лучшей ориентации в вызываемых функциях коммуникационной библиотеки полезно иметь под рукой текст ее описания.

    Текст программной реализации метода находится в файле poissn_soft.c. Этот текст в действительности содержит в себе два разных текста: текст реализации метода на единичном процессоре и текст реализации метода на SMP-узле при помощи OpenMP.

    При трансляции обычным компилятором, в котором поддержка OpenMP отсутствует или выключена, директивы #pragma игнорируются, и получается реализация метода на единичном процессоре. Если мы захотим запустить это приложение на МВС-экспресс, каждый вычислительный узел которой содержит по 3 доступных пользователю процессорных ядра, есть смысл воспользоваться командой запуска на счет, порождающей по 3 пользовательских процесса на каждом узле установки (в полном соответствии с Руководством пользователя).

    При трансляции этого же исходного текста компилятором с работающей поддержкой OpenMP мы получим реализацию метода на SMP-узле, то есть параллельную программу с гибридным параллелизмом. Каждую ветвь такой программы следует запускать на одном вычислительном узле МВС-экспресс. Использование для параллельного счета того факта, что узел содержит несколько процессорных ядер, достигается средствами OpenMP.

    Наконец, можно реализовать метод на векторно-мультитредовом ускорителе CUDA, входящем в состав каждого из узлов МВС-экспресс. По техническим причинам, упомянутым в Руководстве пользователя, общую часть приложения придется оформить как программу на C++, хотя по существу это программа на C. Файл progrev_shmem.cpp отличается от варианта из общей части приложения для программной реализации только расширением («.cpp» вместо «.c») и именем включаемого файла прототипов библиотечных функций (shmem++.h вместо shmem.h). В этом случае каждая ветвь приложения запускается на отдельном узле МВС-экспресс, поскольку она (ветвь) нуждается в отдельном ускорителе CUDA, а в составе узла такой ускоритель один. Текст реализации метода на ускорителе находится в poissn_cuda.cu. При рассмотрении этого текста важно понимать следующее.

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

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

    Чтобы облегчить понимание текста для ускорителя, следует рассмотреть вариант этого текста для универсального процессора, переписанный в такой форме, чтобы его перенос на ускоритель дал максимальный эффект. Отличия сосредоточены, в основном, в функции poissn(). Сводятся они к тому, что в программе организуются три вспомогательных массива, которые содержат предыдущую, текущую и следующую обрабатываемую строки матрицы. При переписывании для ускорителя именно эти вспомогательные массивы будут размещены в быстрой, расслоенной памяти ускорителя, что и позволит достичь максимального  ускорения. Отметим, что эта техника реализации сама по себе способна несколько ускорить расчет, даже на универсальном процессоре. На ускорителе же эффект от ее применения является многократным.


    Приложения

    CPUOpenMPCUDAВсе примеры одним файлом
    poissn.h poissn.h poissn.h jacoby.tar
    poissn_soft.c poissn_soft.c
    progrev_shmem.c progrev_shmem.c progrev_shmem.cpp
    poissn_cuda.cu poissn_cuda.cu
 
 
 
 
 
 
 
 
  Тел. +7(499)220-79-72; E-mail: inform@kiam.ru