Появление таких кластеров с ГПУ, как К-100 (ИПМ им.М.В.Келдыша РАН) и "Ломоносов" (НИВЦ МГУ) остро поставило вопрос о расширении модели DVM - разработке модели DVMH (DVM for Heterogeneous systems).

В 2011 году была разработана упрощенная версия языка Fortran DVMH и создана экспериментальная версия компилятора, который переводит программу на языке Fortran DVMH в программу на языке PGI Fortran CUDA.

Язык Fortran DVM расширен следующими директивами:

1) Определение вычислительного региона

!dvm$ region clause {, clause}

<structured block>

!dvm$ end region

Внутри вычислительного региона не может быть других вычислительных регионов.

В качестве клауз может быть задано:

а) in(subarray_or_scalar), out(subarray_or_scalar), inout(subarray_or_scalar), local(subarray_or_scalar)

Указание характеристики использования подмассивов и скаляров в регионе.

out - по выходу из региона значение переменной изменяется, причем это изменение может быть использовано далее,

in - при входе в регион нужны актуальные данные,

inout(subarray_or_scalar) - сокращенная запись двух клауз - in и out,

local - копирование данных между ЦПУ и ГПУ не требуется.

б) targets(target_name:condition {, target_name:condition})

Указание условий для вычислителей. При выборе вычислителя для региона будет сначала проанализирован список указанных вычислителей и в порядке указания будут вычислены условия. Первый, прошедший тест условия, будет выбран как исполнитель. Если ни один из вычислителей не прошел тест условия, регион будет выполнен на хосте (ЦПУ). Если клауза targets отсутствует, то выбор будет осуществляться из всех доступных вычислителей автоматически. Набор доступных на данный момент вычислителей: HOST, CUDA.

После выбора исполнителя региона автоматически будет определены и выполнены операции по запросу памяти для подмассивов и скаляров (у которых не было представителя на выбранном вычислителе) и операции по обновлению входных данных.

2) Задание свойств цикла и правил отображения витков цикла на ускоритель

!dvm$ do clause {, clause}

<DVM-loop nest>

В качестве клауз может быть задано:

а) private(array_or_scalar)

Объявляет переменную приватной (локальной для каждого витка цикла).

б) firstprivate(array_or_scalar)

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

в) cuda_block(dim3)

Задает размер блока нитей для вычислителя CUDA. Может указываться целочисленное выражение - тогда блок полагается одномерным, может указываться два или три целочисленных выражения через запятую - соответственно блок будет иметь указанную размерность.

3) Указание об актуализации данных на ЦПУ

!dvm$ get_actual(subarray_or_scalar) делает все необходимые обновления для того, чтобы в памяти ЦПУ были актуальные данные в указанном подмассиве или скаляре (при этом, возможно, ничего и не будет перемещено).

4) Подтверждение актуальности данных на ЦПУ

!dvm$ actual(subarray_or_scalar) объявляет тот факт, что указанный подмассив или скаляр актуальную версию имеет в хост-памяти. При этом все другие представители указанного подмассива или скаляра в памяти ускорителей автоматически устаревают и перед использованием будут обновлены.

5) Регистрация данных, которые могут быть удалены из памяти ГПУ

!dvm$ free(subarray_or_scalar)

 

Библиотека Lib-DVM расширена следующими функциями:

- определение состава ускорителей в момент запуска программы;

- регистрация региона и информации об используемых им данных;

- выделение памяти на ГПУ под данные, используемые в регионе, выполнение которого будет происходить на ГПУ;

- копирование входных данных региона из памяти ЦПУ в память ГПУ, если они там отсутствуют;

- запуск параллельных циклов региона на ГПУ. Если конфигурация блока нитей для цикла не задана в программе, то она определяется автоматически;

- копирование выходных данных региона из памяти ГПУ в память ЦПУ, если они там требуются для выполнения программ на ЦПУ;

- регистрация данных, которые могут быть удалены из памяти ГПУ;

- освобождение памяти на ГПУ в случае ее нехватки для размещения требуемых данных.

Результаты экспериментов

Продемонстрируем основные возможности языка Fortran DVMH на примере алгоритма Якоби.

              PROGRAM JAC_GPU

              PARAMETER (L=4096, ITMAX=1000)

              REAL A(L,L), EPS, MAXEPS, B(L,L)

!DVM$ DISTRIBUTE (BLOCK, BLOCK) :: A

!DVM$ ALIGN B(I,J) WITH A(I,J)

!             arrays A and B  with block distribution

              PRINT *,  '**********  TEST_JACOBI   **********'

              MAXEPS  =  0.5E - 7

!DVM$ REGION OUT(A), OUT(B), IN(L)

!DVM$   PARALLEL (J,I) ON A(I, J)

!               nest of two parallel loops, iteration (i,j) will be executed on

!               processor, which is owner of element A(i,j)

                DO J = 1, L

                  DO I = 1, L

                    A(I,  J) = 0.

                    IF(I.EQ.1 .OR. J.EQ.1 .OR. I.EQ.L .OR. J.EQ.L) THEN

                      B(I, J) = 0.

                    ELSE

                      B(I, J) = (1. + I + J)

                    ENDIF

                  ENDDO

                ENDDO

!DVM$ END REGION

              DO  IT  =  1,  ITMAX

!DVM$    REGION IN(A(2:L-1, 2:L-1), OUT(A), INOUT(B(2:L-1, 2:L-1)), OUT(EPS)

                   EPS  =  0.

!DVM$      PARALLEL (J, I) ON A(I, J), REDUCTION (MAX(EPS))

!                  variable EPS is used for calculation of maximum value

                   DO J =  2, L-1

                     DO I =  2, L-1

                       EPS = MAX(EPS, ABS(B( I, J) - A( I, J)))

                       A(I, J) = B(I, J)

                     ENDDO

                   ENDDO

!DVM$      PARALLEL (J, I) ON B(I, J), SHADOW_RENEW (A)

!                  Copying shadow elements of array A from

!                  neighbouring processors before loop execution

                   DO J = 2, L-1

                     DO I = 2, L-1

                       B(I, J) =  (A( I-1, J) + A(I, J-1) + A(I+1, J) + A(I, J+1)) / 4

                     ENDDO

                   ENDDO

!DVM$    END REGION

!DVM$    GET_ACTUAL (EPS)

                 PRINT 200, IT, EPS

200           FORMAT(' IT = ',I4, ' EPS = ', E14.7)

                 IF (EPS .LT. MAXEPS) GO TO 3

              ENDDO

3            CONTINUE

!DVM$ GET_ACTUAL(B)

              OPEN (3, FILE='JAC.DAT', FORM='FORMATTED', STATUS='UNKNOWN')

              WRITE (3,*)   B

              CLOSE (3)

              END

В таблице 1 приводятся времена выполнения вариантов программы JAC_GPU на одном узле кластера К-100 (использовался компилятор PGI с опцией -О2).

В узле кластера К-100 имеются два 6-ядерных процессора Intel Xeon X5670 и 3 графических процессора NVIDIA Tesla C2050. Поскольку одно ядро выделено для специальных целей, то на узле можно запустить 11 MPI-процессов или 11 нитей OpenMP.

Программа на языке Fortran DVMH была запущена как последовательная (столбец Serial), как DVM-программа (столбец DVM) и как DVMH-программа (столбец DVMH). Кроме того, были созданы и пропущены еще три варианта программы - на языке PGI Fortran APM, на языке PGI Fortran CUDA и на языке Fortran OpenMP.

Таблица 1. Времена выполнения программы JAC_GPU (сек) на кластере K-100.

Число ядер

Serial

DVM

DVMH

(32x16x1)

PGI_APM

Fortran CUDA (32x16x1)

OpenMP

1

46.22

51.13

7.68

13.68

5.52

46.04

2

 

25.70

4.18

 

 

23.15

3

 

18.65

3.12

 

 

19.62

4

 

15.29

 

 

 

13.84

8

 

10.55

 

 

 

11.70

11

 

12.26

 

 

 

11.01

 

Экспериментальная версия компилятора уже позволила проводить эксперименты и с реальными приложениями. На языке Fortran DVMH была реализована задача о течении несжимаемой жидкости или слабо сжимаемого газа около прямоугольной выемки, в которой в качестве исходной математической модели используется гиперболический вариант квазигазодинамической системы. Были реализованы двумерная и трехмерная задачи - Каверна и Контейнер соответственно.

Для задачи Каверна в результате расчетов на ГПУ NVIDIA Fermi C2050 для сеток различного размера были получены ускорения по сравнению с процессором Intel Xeon X5670, изображенные на рис. 1.

Рис. 1. Ускорение программы Каверна на различных размерах сетки на одном ГПУ K-100.

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

На рис. 2 и 3 приводятся ускорения выполнения программ Каверна (для рассчетной сетки размерности 1600x1600) и Контейнер (для рассчетной сетки размерности 120x120x120) при использовании различного числа графических процессоров кластера К-100.

Рис. 2. Ускорение программы Каверна на сетке 1600x1600 для разного числа ГПУ.

Рис. 3. Ускорение программы Контейнер на сетке 120x120x120 для разного числа ГПУ.