Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Алгоритм Штрассена

.pdf
Скачиваний:
105
Добавлен:
15.04.2015
Размер:
543.74 Кб
Скачать

C (1:P ,

1: P) = strassen ( A11

+

A22 ,

B11

 

+ B22 ,

P) + strassen (A22 ,

&

 

 

B21 -B11 ,

 

P) + strassen ( A12

- A22 ,

B21

+

B22 ,

P)

- strassen ( A11 +

A21

,

B22 , P)

C (1:P ,

P +1: N) = strassen (A11 ,

B12

- B22 , P)

+

strassen ( A11 + A21 , B22 , P)

C (P +1:N

,

1: P) = strassen ( A21

+ A22 , B11 ,

P)

+ strassen (A22 , B21 -B11

, P)

C (P +1:N

,

P +1: N) = strassen ( A11 +

A22 ,

 

B11

+

B22 , P) - strassen ( A21

+

A22 ,&

B11 , P)

+

strassen (A11 , B12

-

B22 ,

P)

+

strassen ( A21 - A11 , B11

+ B12 ,

P)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Разберем тут же еще один вариант. Этот вариант не предполагает объявления семи матриц M1 M7, и тогда четыре блока матрицы C можно найти следующим образом:

C (1:P ,

1: P) = strassen ( A11

+

A22 ,

B11

 

+ B22 ,

P) + strassen (A22 ,

&

 

 

B21 -B11 ,

 

P) + strassen ( A12

- A22 ,

B21

+

B22 ,

P)

- strassen ( A11 +

A21

,

B22 , P)

C (1:P ,

P +1: N) = strassen (A11 ,

B12

- B22 , P)

+

strassen ( A11 + A21 , B22 , P)

C (P +1:N

,

1: P) = strassen ( A21

+ A22 , B11 ,

P)

+ strassen (A22 , B21 -B11

, P)

C (P +1:N

,

P +1: N) = strassen ( A11 +

A22 ,

 

B11

+

B22 , P) - strassen ( A21

+

A22 ,&

B11 , P)

+

strassen (A11 , B12

-

B22 ,

P)

+

strassen ( A21 - A11 , B11

+ B12 ,

P)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

 

size

64

128

256

512

1024

 

без M1 M7

8.00000038E-03

6.40039966E-02

0.81605101

9.7846117

117.51535

 

с M1 M7

0.0000000

1.19999992E-02

9.60060060E-02

0.66004199

4.6242890

Из таблицы очень хорошо видно, что вариант без M1 M7 по времени отстает очень сильно. Заметно, что сейчас алгоритм Штрассена работает медленнее, чем умножение с двумя циклами. Так получается потому, что выход из рекурсии происходит очень поздно. Оставим пока этот вопрос, потому что на данном этапе возникла более важная проблема. Если попробовать умножить с помощью этой программы матрицы размера 2048 2048, оставив тот же выход из рекурсии (это 8), то возникает следующая ошибка:

Operating system error : Cannot allocate memory

Allocation would exceed memory limit

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

11

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

program algorithm

 

 

implicit none

 

 

real , dimension (: ,:) , allocatable :: first ,

second , third

interface

 

 

recursive

subroutine strassen (A , B ,

C , S)

integer ,

intent ( in ):: S

 

real , intent ( inout ), allocatable :: A(: , :) , B(: , :) real , intent ( inout ), allocatable :: C(: , :)

end subroutine end interface

integer size1 , size2 , size3 , m , N , i , j , k real x , start_strassen , finish_strassen

size1 =100 size2 =120 size3 =102

m= max ( size1 , size2 , size3 )

do i = 2, 100

do

while (m . le . 2**( i ))

 

N =2**( i)

 

go to 11

end

do

end do

11 allocate ( first (N , N )) allocate ( second (N , N )) do i=1 , size1

do j=1 , size2

call random_number (x) first (i ,j )= x *10

end do

end do

do i=1 , size2

do j=1 , size3

call random_number (x) second (i , j )= x *10

end do

end do

first ( size1 :N , size2 :N) = 0. second ( size2 :N , size3 :N) = 0. call cpu_time ( start_strassen )

call strassen ( first , second , third , N) call cpu_time ( finish_strassen )

print *, ’stassen calc time is ’, finish_strassen - start_strassen deallocate ( third )

end

Матрицы first и second играют роль A и B, память под эти матрицы выделяется в главной программе, потом они заполняются случайными значениями и дополняются нулевыми строками и столбцами, освобождение памяти от этих матриц происходит во время выполнения алгоритма во внешней процедуре. Для результирующей матрицы third происходит наоборот: память для нее выделяется в алгоритме(подпрограмме), а освобождается после вызова внешней подпрограммы из главной. Выделение и освобождение памяти должно происходить вовремя, и, самое

12

главное, один раз. Далее приведён текст подпрограммы.

recursive subroutine

strassen (A , B , C ,

 

S)

 

implicit none

 

 

 

 

 

 

 

integer , intent ( in ):: S

 

 

 

 

 

integer P , i , j , k

 

 

 

 

 

 

 

real , intent ( in out ), allocatable :: A(: , :) , B(: , :)

 

real , intent ( in out ), allocatable :: C(: , :)

 

real ,

 

allocatable ::

M1 (: , :) ,

M2 (: , :) , M3 (: , :) , &

 

 

 

 

 

 

 

M4 (: ,

:) , M5 (: ,

:) , M6 (: , :) , M7 (: ,

:) , AT (: , :)

real ,

 

allocatable ::

A1 (: , :) ,

A2 (: , :) , A3 (: , :) , &

 

 

 

 

 

 

 

A4 (: ,

:) , A5 (: ,

:) , A6 (: , :) , A7 (: , :)

real ,

 

allocatable ::

B1 (: , :) ,

B2 (: , :) , B3 (: , :) , &

 

 

 

 

 

 

 

B4 (: ,

:) , B5 (: ,

:) , B6 (: , :) , B7 (: ,

:)

if (S

 

== 16) then

 

 

 

 

 

 

 

 

 

 

allocate ( AT (S , S ))

 

 

 

 

 

 

 

AT = transpose (A)

 

 

 

 

 

 

 

allocate (C(S , S ))

 

 

 

 

 

 

 

do i = 1, S

 

 

 

 

 

 

 

 

 

 

do

j

= 1, S

 

 

 

 

 

 

 

end

do

C(j , i) = sum ( AT (: , j )* B(: , i ))

 

 

 

 

 

 

 

 

 

 

 

 

end do

 

 

 

 

 

 

 

deallocate (AT , B , A)

 

 

 

 

 

 

return

 

 

 

 

 

 

 

 

end

if

 

 

 

 

 

 

 

 

P=S /2

 

 

 

 

 

 

 

 

 

allocate ( A1 (P , P),

A2 (P ,

P),

A3 (P , P), A4 (P , P), &

 

 

 

 

 

 

 

 

A5 (P ,

P), A6 (P , P), A7 (P , P ))

A1

=

A

(1:P , 1: P) +

A (P +1:S , P +1: S)

 

 

 

A2

=

A

(P +1:S , 1: P)

+

A

(P +1:S , P +1: S)

 

 

 

A3

=

A

(1:P , 1: P)

 

 

 

 

 

 

 

A4

=

A

(P +1:S , P +1: S)

 

 

 

 

 

 

A5

=

A

(1:P , 1: P) +

A (1:P , P +1: S)

 

 

 

A6

=

A

(P +1:S , 1: P)

- A (1:P ,

1: P)

 

 

 

A7

=

A

(1:P , P +1: S)

-

A

(P +1:S , P +1: S)

 

 

 

deallocate (A)

 

 

 

 

 

 

 

allocate ( B1 (P , P),

B2 (P ,

P),

B3 (P , P),

 

B4 (P , P), B5 (P , P), &

 

 

 

 

 

 

 

 

B6 (P ,

P), B7 (P , P ))

 

B1

=

B

(1:P , 1: P) +

B

(P +1:S ,

P +1: S)

 

 

 

B2

=

B

(1:P , 1: P)

 

 

 

 

 

 

 

B3

=

B

(1:P , P +1: S)

-

B (P +1:S , P +1: S)

 

 

 

B4

=

B

(P +1:S , 1: P)

- B (1:P , 1: P)

 

 

 

B5

=

B

(P +1:S , P +1: S)

 

 

 

 

 

 

B6

=

B

(1:P , 1: P) +

B (1:P , P +1: S)

 

 

 

B7

=

B

(P +1:S , 1: P)

+

B

(P +1:S , P +1: S)

 

 

 

deallocate (B)

13

call strassen (A1 , B1 , M1 , P) call strassen (A2 , B2 , M2 , P) call strassen (A3 , B3 , M3 , P) call strassen (A4 , B4 , M4 , P) call strassen (A5 , B5 , M5 , P) call strassen (A6 , B6 , M6 , P) call strassen (A7 , B7 , M7 , P)

allocate (C(S , S ))

C (1:P ,

1: P) =

M1

+

M4

+

M7

- M5

C (1:P ,

P +1: S)

=

M3

+

M5

 

 

C (P +1:S ,

1: P)

=

M2

+

M4

 

 

C (P +1:S ,

P +1: S) = M1

-

M2 +

M3 + M6

end subroutine

 

 

 

 

 

 

В этой подпрограмме алгоритм реализован немного по-другому, нежели в предыдущей. Здесь явно вычисляются матрицы B1 B7 и A1 A7, которые потом используются для рекурсивной подпрограммы внутри нее самой. А M1 M7 - неявные результаты внутренних вызовов, из которых потом складывается результирующая матрица C. Эта программа позволяет умножать матрицы размера не более 8192 8192. Почему? Одна матрица размера 16384 16384 (это 214) занимает 1024 Мб, т.к. в такой матрице 228 элементов, каждый по 4 байта, всего выходит 230 байт занимает одна матрица. А 230 байт - это 210 мегабайт, а это и есть 1024. Размер оперативной памяти моего комьютера - 4 Гб, чего будет не достаточно. При вызове подпрограммы требуется две матрицы, т.е. больше 2 Гб будет занято. В ходе алгоритма нужно ввести семь матриц в два раза меньше - это чуть менее 2 Гб. На этом ресурсы исчерпаны, так что придется ограничиться матрицами 8192 8192. Вообще-то, это, конечно, не предел. Увеличить оперативную память системы возможно, выделив для нее часть жесткого диска. Это, конечно, замедлит работу программы.

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

14

 

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

 

 

128

 

 

выход

8

16

 

32

64

 

алгоритм

1.60009991E-02

8.00000038E-03

 

8.00000038E-03

4.00000019E-03

 

two loops mul

 

0.0000000

 

 

 

 

 

 

 

 

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

 

 

265

 

 

выход

16

32

 

64

128

 

алгоритм

3.20019983E-02

2.00009998E-02

 

1.60009991E-02

1.20009994E-02

 

two loops mul

 

1.60010010E-02

 

 

 

 

 

 

 

 

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

 

 

512

 

 

выход

32

64

 

128

256

 

алгоритм

0.14400901

0.10400599

 

8.80050063E-02

8.00050050E-02

 

two loops mul

 

9.20059979E-02

 

 

 

 

 

 

 

 

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

 

 

1024

 

 

выход

64

128

 

256

512

 

алгоритм

0.73204601

0.60403895

 

0.61603898

0.68404299

 

two loops mul

 

0.69604409

 

 

 

 

 

 

 

 

До 1024 лучший результат алгоритма достагается при самом позднем выходе из рекурсии, для 1024 1024 получилось, что оптимальный выход в середине, при размере 128. Разница во времени хоть и есть, но она все равно очень мала. Кроме этого, я хочу обратить внимание на один очень важный фактор. Время работы умножения с двумя циклами в предыдущих таблицах дано для матриц наибольшего возможного размера - 128, 256 и т.д. Но на практике матрицы размера степени двойки встречаются не очень часто, а значит, чтобы умножить, к примеру, две матрицы 700 700 по алгоритму, придется дополнять их нулями, в то время как умножение двумя циклами справится с этой задачей быстрее. Вывод напрашивается сам собой - пока алгоритм Штрассена не эффективен. Посмотрим, что будет дальше.

 

size

 

 

2048

 

 

 

 

выход

32

64

128

 

256

512

1024

 

алгоритм

6.9844356

5.1683230

4.2442646

 

4.4402766

4.9003067

5.1523228

 

two loops mul

 

 

5.7003570

 

 

На этот раз алгоритм Штрассена обгоняет two loops mul на 1,45 секунды, что уже можно назвать существенным. Но стоит заметить, что использовать алгоритм для матриц меньше 1900 (two loops mul - 4.6042881) не рационально, т.к. умножение с двумя циклами справится с этой задачей быстрее.

15

 

size

 

 

4096

 

 

 

 

выход

64

128

256

 

512

1024

2048

 

алгоритм

49.219078

36.686291

30.041878

 

31.457966

34.538158

36.854305

 

 

 

 

 

 

 

 

 

size

 

 

8192

 

 

 

 

выход

64

128

256

 

512

1024

2048

 

алгоритм

257.54810

211.40521

220.90981

 

242.57515

256.85605

293.05032

 

 

 

 

 

 

 

 

 

Стоит заметить, что в данном случае оптимальный выход из рекурсии - 256. Посмотрим теперь, как ведет себя простое умножение. Будем брать средний размер матриц между текущим и предыдущим, потом, в зависимости от результата, еще в два раза меньше или больше.

 

size

3072

3584

6140

7164

6652

6908

 

time

19.441214

30.349897

157.74586

249.81161

200.78856

221.72986

Дополнять матрицы до 4096 стоит начиная, примерно, с 3600, а до 8196 - с 6900, что хорошо видно из таблицы.

Итак, подведем итоги. Нельзя назвать точный размер матриц, с которого нужно начинать использовать алгоритм Штрассена. Зато получились некоторые промежутки или, точнее, три множества матриц, для которых алгоритм выгоден, а именно:

1900 2048;

3600 4096;

6900 8192.

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

16

Список литературы

[1]Дж. Макконнел. Основы современных алгоритмов.

[2]А. В. Левитин. Алгоритмы: введение в разработку и анализ.

[3]Кормен, Лейзерсон, Ривест, Штайн. Алгоритмы: построение и анализ.

[4]Metcalf M., Reid J., Cohen M. Fortran 95 2003 explained

[5]GNU Compiler Collection: Optimize Options http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

[6]GNU Compiler Collection: Intel 386 and AMD x86-64 Options http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86002d64-Options.html

Есть вопросы - пишите ge-alena@yandex.ru. Герасимова Алёна

17