Современные информационные технологии/Вычислительная техника и программирование

Мясищев А.А.

Хмельницкий национальный университет, Украина

О практическом достижении наибольшей производительности при матричном перемножении  для вычислительных систем на базе многоядерных процессоров

 

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

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

         Пусть A,B,C – квадратные матрицы размером n x n.. Матрица C=A*B. Тогда компоненты матрицы C рассчитываются по формуле:

 

Здесь  i ,j=1,…,n..

Для вычисления одного элемента матрицы C необходимо выполнить n умножений и n-1 сложений. Следовательно, общее количество операций с плавающей точкой для всех элементов матрицы определится

Оценку производительности компьютера будем производить в Мегафлопсах (1 Mflops - миллион операций с плавающей точкой за одну секунду). Разделив число операций на время, умноженное на 1000000, получим производительность системы в Мегафлопсах. Для перемножения квадратных матриц получим

     (Mflops)

Наибольшую производительность попытаемся  достичь на вычислительной системе, построенной на базе одного четырех ядерного  процессора CORE 2 QUAD PENTIUM Q6600 2.4GHZ с оперативной памятью 4Гбайт. В качестве операционной системы будем использовать 32-х и 64-х разрядные Linux Ubuntu ver. 9.04 desktop, компиляторы фортран  F77 и gfortran ver. 4.3.3, которые входят в состав этих дистрибутивов

Так как вычислительная система базируется на четырех ядерном процессоре, то программы должны предусматривать процедуры распараллеливания вычислительного процесса. Для этой цели будем использовать библиотеки MPI [1].

         Рассмотрим первый подход. В работе [2] представлен фрагмент программы по умножению матриц:

       do j=1,n

       do k=1,n

       do i=1,n

        c(i,j)=c(i,j)+a(i,k)*b(k,j)

       end do

       end do

       end do

Показано, что при  использовании цикла jki достигается максимальная производительность работы программы для компилятора ФОРТРАН, который размещает элементы матрицы в памяти по столбцам т.е.

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

         Однако данный алгоритм все же недостаточно эффективно использует кеш-память первого уровня [2]. Известно, что ее размер для используемого нами процессора не превышает 32Кбайт для хранения данных на одно ядро. Для эффективного ее использования целесообразно, чтобы вся матрица могла  быть там размещена во время проведения операций умножения. С этой целью рассмотрим блочное представление матриц. Представим матрицы A,B,C (глобальные матрицы) в виде блоков, размером  (n/N)x(n/N), N - количество блоков по строкам или столбцам.

=AB=

  

Здесь  - блочные матрицы.

Ниже представлен фрагмент программы, реализующий блочное умножение матриц [2].  

 do j=1,nn

 do k=1,nn

 do i=1,nn

       do j1=1,m

       do k1=1,m

       do i1=1,m

      c(i1,j1,i,j)=c(i1,j1,i,j)+a(i1,k1,i,k)*b(k1,j1,k,j)

       end do

       end do

       end do

   end do

   end do

   end do

Здесь nn=N,  m=n/N. Первые два индекса (i1,j1) массивов – строки и столбцы конкретной блочной матрицы, последние два индекса (i,j) – координаты блочной матрицы в глобальной матрице.

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

      program cpu4_par

      include 'mpif.h'

C  n-число строк и столбцов матриц A,B,C

C  m-число строк и столбцов в блоке матриц

C  nn-число строк и столбцов матриц A,B,C,составленных из блоков         

      parameter (n=5120,m=20,nn=n/m)

      integer ierr,rank,size,i,j,k

      double precision  a(m,m,nn,nn), b(m,m,nn,nn), c(m,m,nn,nn)

      double precision  mf,op

      double precision time1, time, time2

      integer status(MPI_STATUS_SIZE)

      call MPI_INIT(ierr)

C  size – число ядер

      call MPI_COMM_SIZE(MPI_COMM_WORLD, size, ierr)

C   rank – номер ядра

      call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)

      do 10 i=1,nn

      do 10 j=1,nn

      do 10 i1=1,m

      do 10 j1=1,m

      a(i1,j1,i,j)=1.0d0*(i1+m*(i-1))*(j1+m*(j-1))

      b(i1,j1,i,j)=1.0d0/a(i1,j1,i,j)

      c(i1,j1,i,j)=0.d0

10    continue

      time1 = MPI_Wtime()

c Цикл по процессорам rank

      do j=rank+1,rank+1+(nn-size),size

      do k=1,nn

      do i=1,nn

       do j1=1,m

       do k1=1,m

       do i1=1,m

      c(i1,j1,i,j)=c(i1,j1,i,j)+a(i1,k1,i,k)*b(k1,j1,k,j)

       end do

       end do

       end do

      end do

      end do

      end do

      time2 = MPI_Wtime()

      time=time2-time1

      if(rank.eq.0) then

      op=(2.0*n-1)*n*n

      mf=op/(time*1000000.0)

      print *,' time = ',time, 'mf=',mf

      end if

c  Распечатка элемента матрицы C

      call prinmatr(1000,250,m,nn,c)

      call MPI_FINALIZE(ierr)

      end

c Подпрограмма для распечатки элемента глобальной матрицы

      subroutine prinmatr(ig,jg,m,nn,c)

      double precision c(m,m,nn,nn)     

      il=ig-m*((ig-1)/m)

      jl=jg-m*((jg-1)/m)

      mg=((ig-1)/m)+1

      ng=((jg-1)/m)+1

      print 7,ig,jg,c(il,jl,mg,ng)

7    format(1x,'i=',i5,' j=',i5,' C=',f18.10)

      end 

Компиляция для F77 и запуск программы проводились командами:

F77 –O3 –I/usr/local/mpi/include –f –o cpu4_par cpu4_par.f

/usr/local/mpi/lib/libmpich.a

/usr/local/mpi/bin/mpirun –np 4 –machinefile machines cpu4_par

Для gfortran:

gfortran –O3 –I/usr/lib/lam/include –o cpu4_par cpu4_par –lmpi

mpirunnp 4 cpu4_par

Программа запускалась с ключом  -np 4 (для параллельной работы с 4-я ядрами) и   -np 1 (при работе на 1-м ядре)

Ниже представлены следующие случаи расчета:

1. Числа двойной точности. 64-х разрядная версия Linux Ubuntu ver. 9.04 desktop. Используются компиляторы  F77 и  gfortran. При запуске на одном ядре принимается, что размеры глобальной блочной матриц совпадают, а при запуске на 4-х ядрах (параллельная работа) глобальная матрица разбивается на 16 блоков с целью корректной работы представленной программы. Если глобальная матрица имеет размерность   4000х4000 элементов, то размер блока будет равен 1000х1000 элементов.   Результаты эксперимента показаны в таблице 1. В ней представлены время расчета в секундах произведения (числитель), производительность в Мегафлопсах (знаменатель), ускорение счета для 4-х ядерного варианта.

Таблица 1

Размер матрицы

gfortran

F77

Одно ядро

Четыре ядра

Уско-рение

Одно ядро

Четыре ядра

Уско-рение

500х500

0.09/2653

0.03/8112

3.06

0.12/2126

0.03/8111

3.82

1000х1000

1.42/1405

0.20/9841

7.00

1.57/1272

0.25/8071

6.35

2000х2000

11.81/1355

2.48/6442

4.75

13.00/1231

2.91/5499

4.47

4000х4000

101.9/1256

73.7/1738

1.38

106.9/1198

76.0/1684

1.41

6000х6000

341.0/1267

250.5/1725

1.36

357.9/1206

257.0/1680

1.39

Теоретическое ускорение 4-х ядерного варианта счета должно быть около 4-х единиц. Однако для размера матриц 500х500 оно близко к теоретическому, а для размеров свыше 4000х4000 оно значительно меньше теоретического и настолько, что не имеет смысла вести процедуру распараллеливания программ. Однако, например, для размера 1000х1000 ускорение выше теоретического, что объясняется блочным представлением матрицы. Следовательно, для повышения эффективности при параллельных расчетах необходимо уменьшать размер блока матрицы. Из таблицы также видно, что программа, скомпилированная на  gfortran для 64-х разрядной системы, работает быстрее, чем в случае компиляции на F77.

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

Таблица 2.

Размер матрицы

gfortran

F77

Одно ядро

Четыре ядра

Уско-рение

Одно ядро

Четыре ядра

Уско-рение

1000х1000

-

-

-

0.94/2129

0.23/8704

4.09

1280х1280

1.14/3694

0.29/14425

3.90

-

-

-

2000х2000

4.41/3630

1.15/13952

3.84

7.53/2126

1.56/10231

4.81

2560х2560

9.27/3618

2.34/14359

3.97

-

-

-

4000х4000

35.4/3620

9.47/13509

3.73

60.5/2114

15.0/8548

4.04

5120х5120

74.4/3607

20.2/13317

3.69

-

-

-

6000х6000

120.4/3588

34.3/12580

3.51

205.5/2102

50.8/8500

4.04

Размер блочных матриц, для которых получалась наибольшая производительность для компилятора gfortran, примерно равен 20х20, для F77 – 200х200 элементов. Из таблицы 2 видно, что gfortran примерно в 1.5 раза для 64-х разрядных систем эффективнее, чем F77. Ускорение счета при параллельном вычислении для 4-х ядерного процессора примерно соответствует теоретически достижимому,  т.е. – четырем. Это свидетельствует о том, что кеш-память используется эффективно. Для матриц большого размера (> 4000х4000) производительность возросла более чем в 7 раз. С увеличением размера матрицы производительность слабо уменьшается.

3. Здесь рассматривается те же условия, что и во 2-м случае, однако расчеты проводятся для чисел одинарной точности и для 4-х ядер. Результаты представлены в таблице 3.

Таблица 3.

Размер

матрицы

gfortran

F77

Четыре ядра

Четыре ядра

1280х1280

0.14/30534

-

2000х2000

0.51/31160

0.79/20150

4000х4000

4.34/29465

6.36/20138

6400х6400

17.8/29488

32.5/16131

8000х8000

35.9/28551

63.4/16139

Из таблицы 3 видно, что для чисел одинарной точности производительность по сравнению с числами двойной точности удваивается. Для больших матриц (> 6000х6000) компилятор gfortran почти в два раза эффективнее F77 для 64-х разрядных систем.

4. Числа двойной точности. 32-х разрядная версия Linux Ubuntu ver. 9.04 desktop. Используются компиляторы  F77 и  gfortran. Размер блочных матриц выбирается из условия достижения наибольшей производительности расчетов, как для последовательного одноядерного варианта, так и для параллельного многоядерного. Результаты эксперимента представлены в таблице 4.

Таблица 4.

Размер матрицы

gfortran

F77

Одно ядро

Четыре ядра

Уско-рение

Одно ядро

Четыре ядра

Уско-рение

1000х1000

1.17/1705

0.24/8460

4.96

1.06/1878

0.24/8486

4.52

2000х2000

9.39/1704

1.89/8474

4.97

8.23/1939

1.76/9088

4.67

4000х4000

75.2/1702

18.9/6772

3.98

66.2/1932

16.7/7672

3.97

Из таблицы 4 видно, что для 32-х разрядных систем компилятор F77 работает несколько быстрее gfortran. Сопоставление таблиц 2 и 4 показывает, что для 64-х разрядных систем компилятор gfortran примерно в 1.5-1.8 раза показывает большую производительность, чем для 32-х. Компилятор F77 для 64-х разрядных систем не дает существенного выигрыша в производительности по сравнению с 32-х разрядными.  

5.Условия расчета, что и для 4-го случая, но рассматриваются числа одинарной точности. Расчеты проводятся для 4-х ядер в параллельном режиме. Результаты представлены в таблице 5.

Таблица 5

Размер

матрицы

gfortran

F77

Четыре ядра

Четыре ядра

1000х1000

0.24/8274

0.22/9087

2000х2000

1.86/8584

1.62/9848

4000х4000

14.4/8864

12.7/10082

6400х6400

75.3/6962

66.5/7887

Из таблицы 5, как и для предыдущего случая, видно, что для 32-х разрядных систем компилятор F77 работает несколько быстрее gfortran. Сопоставление таблиц 4 и 5 показывает, что для чисел двойной и одинарной точности для 32-х разрядных систем разница в скорости расчетов не существенная.

Выводы.

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

2.Использование 64-х разрядных систем позволяет в 1.5-1.8 раз поднять производительность расчетов по сравнению с 32-х разрядными.

3.Для 64-х разрядных систем значительно эффективнее использовать компилятор gfortran, который по сравнению с F77 дает прирост производительности в 1.5-1.8 раза. Для 32-х разрядных систем компилятор F77 работает немного быстрее  gfortran.

4.Матричные вычисления одинарной точности в 64-х разрядных системах выполняются в 2.0-2.2 быстрее, чем двойной точности. Для 32-х разрядных систем существенного увеличения быстродействия не замечено.

5.Результаты вычислительного эксперимента показали, что переход к блочному представлению матриц, к 64-х разрядному программному обеспечению, к компилятору gfortran позволило повысить скорость расчетов для чисел одинарной и двойной точности примерно в 8-9 раз.

 

Литература.

1.Гергель В.П. Теория и практика параллельных вычислений. http://www.intuit.ru/department/calculate/paralltp/  - 2007

2.Высокопроизводительные вычисления на кластерах: Учебное пособие/ Под ред. А.В.Старченко.- Томск: Изд-во Том. Ун-та, 2008 – 198 с.

.