Вы находитесь на странице: 1из 6

Реализация алгоритма умножения матриц по Винограду на языке Haskell

Реализация алгоритма умножения матриц по Винограду


на языке Haskell
Анисимов Н.С., МГТУ им. Баумана
anisimoff.nikita@gmail.com
Строганов Ю.В., МГТУ им. Баумана
stroganovyv@bmstu.ru

Параметрический полиморфизм позволяет


Аннотация давать участку кода обобщенный тип, ис-
В данной работе рассматриваются реализа-
пользуя переменные вместо настоящих типов,
ции алгоритма умножения матриц на языке а затем конкретизировать, замещая перемен-
программирования Haskell. Рассматривает- ны типами. Параметрические определения
ся наивная реализация алгоритма умноже- однородны: все экземпляры данного фраг-
ния матриц, алгоритм Винограда и его мента кода ведут себя одинаково [5].
улучшения с помощью использования
средств компилятора, уменьшения числа 2 Описание алгоритма
вызовов функций, а также переход от лени-
вых вычислений к строгим. Наивная реализация умножение матриц на
языке Haskell представлена в листинге 1.
1 Введение 1 mult :: Num a => [[a]] ->
[[a]] -> [[a]]
Умножение матриц является основным ин- 2 mult m1 m2 = result
струментом линейной алгебры и имеет мно- 3 where
гочисленные применения в математике, фи- 4 result = [[sum $ zipWith (*)
a’ b’| b’ <- (transpose m2)] |
зике, программировании. Одним из самых a’ <- m1]
эффективных по времени алгоритмов умно-
жения матриц является алгоритм Винограда, Листинг 1. Наивная реализация умножение мат-
имеющий асимптотическую сложность риц
[1]. Также существуют улучшения В данном фрагменте Num a – это ограни-
этого алгоритма [2] [3]. чение типа, сообщающее о том, что в каче-
Haskell – это функциональный язык. Он стве типа, может быть использован исключи-
воплощает понятие чистоты, модель его вы- тельно числовой тип.
числений основана на концепции “лени”, об- Плата за столь короткую реализацию алго-
ладает параметрическим полиморфизмом [4]. ритма является высокое время выполнения.
Все функции в Haskell представляют собой Для матриц размером 500х500 время выпол-
выражения, подобные математическим. Они нения уже выше трех секунд. Одним из спо-
не имеют побочных эффектов (за некоторыми собов уменьшения времени выполнения явля-
исключениями): об этих выражениях принято ется использование более эффективного ал-
говорить, что они чистые. Это означает от- горитма умножения матриц – алгоритма Ви-
сутствие каких-либо переменных, что непре- нограда.
менно ведет к отсутствию циклов, которые Каждый элемент результирующей матрицы
так активно используются в алгоритмах представляет собой скалярное произведение
умножения матриц. Данный недостаток ком- соответствующих строки и столбца.
пенсируется множеством встроенных функ- Рассмотрим два вектора:
ций. (1)
Haskell реализует ленивую модель вычис- (2)
лений. Выражение на языке Haskell – это Их скалярное произведение равно:
лишь обещание того, что оно будет вычисле- (3)
но при необходимости. Одной из проблем
ленивых вычислений является использование
большого количества памяти, так как необхо- (4)
димо хранить целое выражение для последу- (5)
ющего вычисления.

- 390 -
Новые информационные технологии в автоматизированных системах – 2018

В выражении (4) и (5) требуется большее


число вычислений, чем в первом, но оно поз-
воляет произвести предварительную обработ-
ку. Выражения и для
можно вычислить заранее для каждой соот-
ветствующей строки и столбца. Это позволя-
ет уменьшить число умножений [6].
Схема алгоритма для императивной реали-
зации алгоритма представлена на рисунке 1.

Рис. 2. График времени выполнения multStras-


senStd из Data.Matrix
Шаг 1. На листинге 2 представлен самый
очевидный способ реализации данного алго-
ритма.

1 winograd :: (Num a) =>


Matrix a -> Matrix a -> Matrix a
2 winograd a b = if n' == n then c else
error "error"
3 where
4 m = M.nrows a
5 n = M.ncols a
6 n' = M.nrows b
Рис. 1. Схема алгоритма Винограда для импера- 7 p = M.ncols b
тивного языка 8 rows = V.generate m $ \i -> group $
M.getRow (i + 1) a
Для реализации алгоритма Винограда ис- 9 cols = V.generate p $ \j -> group $
пользован тип Data.Matrix из пакета ma- M.getCol (j + 1) b
trix [7]. Данный тип предлагает обширный 10 group v = foldl (group' v) 0 [0, 2
.. V.length v - 2]
интерфейс для работы с матрицами, и он ос- 11 group' v acc i =
нован на типе Data.Vector [8], который 12 acc - V.unsafeIndex v i * V.un
является стандартным типом для работы с safeIndex v (i+1)
массивами с целочисленной индексацией. 13 c = M.matrix m p $ \(i,j) ->
14 V.unsafeIndex rows (i-1) +
В реализации алгоритма умножения мат- 15 V.unsafeIndex cols (j-1) +
риц в модуле Data.Matrix, использована 16 helper (M.getRow i a) (M.getCol j
директива SPECIALIZE [9] для типов Int, b) +
17 if odd n then M.unsafeGet i n a *
Double, Rational. Она явно специализи- M.unsafeGet n j b
рует функцию под данные типы, чтобы про- 18 else 0
цессорное время не тратилось на обеспечение
работы полиморфизма. Так как реализация 19 helper r c = foldl (helper' r c) 0
[0, 2 .. V.length r - 2]
представлена в виде отдельного модуля, для 20 helper' r c acc i = let
ускорения работы необходимо специализиро- 21 y1 = V.unsafeIndex c (i)
вать разработанную функцию под некоторые 22 y2 = V.unsafeIndex c (i+1)
типы, как это сделано в модуле Da- 23 x1 = V.unsafeIndex r (i)
24 x2 = V.unsafeIndex r (i+1)
ta.Matrix. 25 in
Наиболее эффективной функцией умноже- 26 acc +(x1+y2)*(x2+y1)
ния матриц, представленной модулем
Листинг 2. Первая версия реализации алгоритма
Data.Matrix является Винограда
multStrassenMixed [7]. Время ее выпол-
нения взято за эталонное и демонстрировано Рассмотрим эту реализацию детальнее. В
на рисунке 2. строках (3) - (6) идет получение размеров
матриц. Строки (8) - (13) выполняют предва-

- 391 -
Реализация алгоритма умножения матриц по Винограду на языке Haskell

рительное вычисление, предусмотренное ал- 1 a' = V.generate m $ \i -> M.getRow


(i+1) a
горитмом Винограда. В строках (13) - (18) 2 b' = V.generate p $ \j -> M.getCol
выполняется вычисление результирующей (j+1) b
матрицы c. 3 rows = V.generate m $ \i -> group $
V.unsafeIndex a' i
4 cols = V.generate p $ \j -> group $
V.unsafeIndex b' j
5 group v =foldl (group' v) 0 [0, 2 ..
V.length v - 2]
6 group' v acc i = acc - V.unsafeIndex
v i * V.unsafeIndex v (i+1)

7 c = M.matrix m p $ \(i,j) ->


8 V.unsafeIndex rows (i-1) +
9 V.unsafeIndex cols (j-1) +
10 helper (V.unsafeIndex a' (i-1))
(V.unsafeIndex b' (j-1)) +
11 if odd n then M.unsafeGet i n a *
M.unsafeGet n j b
12 else 0
Листинг 3. Предварительное вычисление столбцов
и строк.
Рис. 3. Время выполнения реализации алгоритма
Винограда
Как видно из рисунка 3, она уступает
функции multStrassenMixed. Одна из
проблем заключается в лишних вызовах
функций getRow и getCol в строках (8), (9)
и (18). Если вызов функции getRow является
быстрой операцией , то вызов функции
getCol является затратным – , где
высота матрицы [7]. Более того, функция
getRow по своей сути является взятием среза
из Data.Vector, на котором основана мат-
рица, а Data.Vector реализует эту опера-
цию эффективно [8]. Функция getCol, в
свою очередь, требует создания нового век- Рис. 4. Время выполнения реализации алгоритма
тора, и использует индексирование матрицы. Винограда
Взятие элемента по индексу в матрице сопро- Шаг 3. Следующий шаг, который можно
вождается большим числом вычислений. предпринять для уменьшения времени вы-
Шаг 2. Решить данную проблему можно полнения – это замена в функциях group и
заранее создав два вектора строк и столбцов helper вызова функции length на значе-
соответствующих матриц, как показано на ние n. Можно заметить, что в эти функции
листинге 3. передаются только строки матрицы a и
На рисунке 4 видно, что результат лучше столбцы матрицы b, а они имеют одинаковую
предыдущего, но все еще далек от эталонно- размерность n.
го. Произведя данную замену, можно полу-
чить результат, который уже сопоставим с
эталонным временем, как видно на рисунке 5.

- 392 -
Новые информационные технологии в автоматизированных системах – 2018

Рис. 6. Время выполнения реализации алгоритма


Рис. 5. Время выполнения реализации алгоритма Винограда
Винограда
Шаг 4. Далее можно заметить, что в функ- 1 c = a `deepseq` b `deepseq`
2 if odd n then
цию matrix передается лямбда-функция, в 3 M.matrix m p $ \(i,j) ->
которой есть конструкция if-then-else. 4 V.unsafeIndex rows (i-1) +
Данная функция будет выполнена m*p раз, и, 5 V.unsafeIndex cols (j-1) +
6 helper (V.unsafeIndex a' (i-
следовательно, будет выполнено столько же 1)) (V.unsafeIndex b' (j-1)) +
операций сравнения. Это условие может быть 7 M.unsafeGet i n a *
вынесено за пределы лямбда-функции. M.unsafeGet n j b
8 else
1 c = if odd n then 9 M.matrix m p $ \(i,j) ->
2 M.matrix m p $ \(i,j) -> 10 V.unsafeIndex rows (i-1) +
3 V.unsafeIndex rows (i-1) + 11 V.unsafeIndex cols (j-1) +
4 V.unsafeIndex cols (j-1) + 12 helper (V.unsafeIndex a' (i-
5 helper (V.unsafeIndex a' (i- 1)) (V.unsafeIndex b' (j-1))
1)) (V.unsafeIndex b' (j-1)) +
Листинг 5. Предварительное вычисление a’ и b’.
6 M.unsafeGet i n a *
M.unsafeGet n j b Функция deepseq содержится в модуле
7 else
8 M.matrix m p $ \(i,j) -> Control.DeepSeq, и производит полное
9 V.unsafeIndex rows (i-1) + вычисление своего первого аргумента и после
10 V.unsafeIndex cols (j-1) + этого возвращает второй аргумент. Ее работу
11 helper (V.unsafeIndex a' (i- можно продемонстрировать на следующем
1)) (V.unsafeIndex b' (j-1))
примере:
Листинг 4. Перенос конструкции if-then-else за
пределы лямбда-выражения. 1 Prelude Control.DeepSeq> let a =
[1..10] :: [Int]
Рисунок 6 показывает, что улучшение про- 2 Prelude Control.DeepSeq> :sprint a
изводительности получено, пусть и не высо- 3 a = _
кое. 4 Prelude Control.DeepSeq> deepseq a 0
5 0
Шаг 5. Данный результат можно улучшить 6 Prelude Control.DeepSeq> :sprint a
еще больше, избавившись от “ленивых” вы- 7 a = [1,2,3,4,5,6,7,8,9,10]
числений. Значения a' и b' вычисляются по
Листинг 6. Фрагмент вывода GHCI. Демонстрация
ходу выполнения программы. Можно вычис- работы deepseq.
лить их заранее, тем самым сэкономить вре-
мя, которое требовалось на поддержку “ле- В первой строке создается "обещание" вы-
ни”. числить список в будущем. :sprint a вы-
водит значение без его вычисления. Из ре-
зультата видно, что оно действительно не вы-
числено. deepseq производит полное вы-
числение а. Далее снова используется

- 393 -
Реализация алгоритма умножения матриц по Винограду на языке Haskell

:sprint для вывода значение, и теперь вы- щение к соответствующим столбцам и стро-
ведено полностью вычисленное выражение. кам. Поэтому ускорить вычисления можно
Для работы функции deepseq необходи- следующим образом:
мо изменить сигнатуру:
1 winograd :: (Num a, NFData a) => 1 c = a' `deepseq` b' `deepseq`
Matrix a -> Matrix a -> Matrix a 2 if odd n then
3 M.matrix m p $ \(i,j) ->
NFData a ограничивает тип значения a 4 let
только теми типами, которые могут быть вы- 5 v1 = V.unsafeIndex a' (i-1)
числены полностью. 6 v2 = V.unsafeIndex b' (j-1)
На рисунке 7 видно, что время выполнение 7 in
8 V.unsafeIndex rows (i-1) +
получается еще меньше эталонного. 9 V.unsafeIndex cols (j-1) +
10 helper v1 v2 +
11 V.last v1 * V.last v2
12 else
13 M.matrix m p $ \(i,j) ->
14 let
15 v1 = V.unsafeIndex a' (i-1)
16 v2 = V.unsafeIndex b' (j-1)
17 in
18 V.unsafeIndex rows (i-1) +
19 V.unsafeIndex cols (j-1) +
20 helper v1 v2
Листинг 7. Ускорение вычислений для матриц
с нечетной общей размерностью.
На рисунке 9 видно, что полученное время
выполнения, соизмеримо со временем выпол-
нения этой функции для матриц с четной
Рис. 7. Время выполнения реализации алгоритма размерностью.
Винограда
Шаг 6. Все предыдущие тесты проводи-
лись для матриц с четной общей размерно-
стью. В случае если общая размерность не-
четная необходимо провести дополнительные
вычисления.
В результате время выполнения сильно
ухудшается, о чем свидетельствует рисунок 8.

Рис. 9. Время выполнения реализации алгоритма


Винограда

3 Заключение
В данной работе был реализован алгоритма
Винограда на языке программирования
Haskell. Были рассмотрены трудности, с ко-
торыми может столкнуться программист, при
Рис. 8. Время выполнения реализации алгоритма разработке данного алгоритма и варианты их
Винограда решения. Выполнено 5 итерации улучшения,
Это объясняется тем, что функция индек- за которые время выполнения алгоритма бы-
сирования матрицы, требует много вычисле- ло улучшено более чем в десять раз:
ний [7]. Более того, уже производилось обра-

- 394 -
Новые информационные технологии в автоматизированных системах – 2018

1. Предварительное получение строк и Список литературы


столбцов соответствующих матриц;
1. Kakaradov B. Ultra-Fast Matrix Multiplication:
2. Замена вызова функции length на за-
An Empirical Analysis of Highly Optimized Vec-
ранее вычисленное значение; tor Algorithms [Электронный ресурс] //
3. Вынос конструкции if-then-else за cs.stanford.edu: [сайт]. [2004]. URL:
пределы лямбда-функции; https://cs.stanford.edu/people/boyko/pubs/Matrix
4. Избавление от лишних ленивых вычис- Mult_SURJ_2004.pdf
лений; 2. Stothers A.J. On the Complexity of Matrix
5. Ускорение работы для матриц с нечет- [Электронный ресурс] // era.lib.ed.ac.uk: [сайт].
ной общей размерностью. [2010]. URL:
Также произведено сравнение с наиболее https://www.era.lib.ed.ac.uk/bitstream/handle/184
2/4734/Stothers2010.pdf
эффективным алгоритмом умножения мат-
3. Williams V.V. Multiplying matrices [Электрон-
риц, входящих в состав модуля ный ресурс] // http://theory.stanford.edu: [сайт].
Data.Matrix. По результатам этого срав- [2014]. URL:
нения было выяснено, что реализованный ал- http://theory.stanford.edu/~virgi/matrixmult-f.pdf
горитм превосходит алгоритм из 4. Мена А.С. Изучаем Haskell. Санкт-Петербург:
Data.Matrix для размеров матриц до Питер, 2015. 464 pp.
1000х1000. 5. Пирс Б. Типы в языках программирования.
В качестве дальнейших вариантов улучше- 655 pp.
ния следуют рассмотреть параллелизацию 6. Алгоритм Копперсмита — Винограда [Элек-
тронный ресурс] // ru.math.wikia.com/: [сайт].
данного алгоритма и использование других,
URL:
более специализированных типов данных. http://ru.math.wikia.com/wiki/Алгоритм_Коппе
рсмита_—_Винограда (дата обращения:
23.02.2018).
7. Data.Matrix [Электронный ресурс] //
hackage.haskell.org: [сайт]. URL:
https://hackage.haskell.org/package/matrix-
0.3.5.0/docs/Data-Matrix.html (дата обращения:
23.02.2018).
8. Data.Vector [Электронный ресурс] //
haskell.hackage.org/: [сайт]. URL:
https://hackage.haskell.org/package/vector-
0.12.0.1/docs/Data-Vector.html (дата обраще-
ния: 23.02.2018).
9. Chapter 7. GHC Language Features [Электрон-
ный ресурс] // Haskell.org/: [сайт]. URL:
https://downloads.haskell.org/~ghc/7.0.2/docs/ht
ml/users_guide/pragmas.html (дата обращения:
23.02.2018).

- 395 -

Вам также может понравиться