Поскольку умножение матриц является центральной операцией во многих числовых алгоритмах , много работы было вложено в повышение эффективности алгоритмов умножения матриц . Применение умножения матриц в вычислительных задачах можно найти во многих областях, включая научные вычисления и распознавание образов , а также в, казалось бы, несвязанных задачах, таких как подсчет путей в графе . [1] Для умножения матриц было разработано множество различных алгоритмов на различных типах оборудования, включая параллельные и распределенные системы, где вычислительная работа распределена по нескольким процессорам (возможно, по сети).
Непосредственное применение математического определения умножения матриц дает алгоритм, который требует времени порядка n3 полевых операций для умножения двух матриц размера n × n по этому полю ( Θ( n3 ) в обозначении большого O ). Лучшие асимптотические границы времени, необходимого для умножения матриц, были известны со времен алгоритма Штрассена в 1960-х годах, но оптимальное время (то есть вычислительная сложность умножения матриц ) остается неизвестным. По состоянию на апрель 2024 года [обновлять]лучшая объявленная оценка асимптотической сложности алгоритма матричного умножения составляет время O ( n 2,371552 ) , данное Уильямсом , Сюй, Сюй и Чжоу. [2] [3] Это улучшает оценку времени O( n 2,3728596 ) , данную Алманом и Уильямсом. [4] [5] Однако этот алгоритм является галактическим из-за больших констант и не может быть реализован на практике.
Определение умножения матриц таково: если C = AB для матрицы A размера n × m и матрицы B размера m × p , то C является матрицей размера n × p с элементами
Исходя из этого, можно построить простой алгоритм, который циклически перебирает индексы i от 1 до n и j от 1 до p , вычисляя вышеуказанное с помощью вложенного цикла:
Этот алгоритм требует времени Θ( nmp ) (в асимптотических обозначениях ). [1] Обычное упрощение для целей анализа алгоритма состоит в том, чтобы предположить, что все входные данные представляют собой квадратные матрицы размера n × n , и в этом случае время выполнения равно Θ( n 3 ) , то есть кубическое по размеру размерности. . [6]
Три цикла итеративного умножения матриц можно произвольно менять местами без влияния на корректность или асимптотическое время выполнения. Однако порядок может оказать значительное влияние на практическую производительность из-за шаблонов доступа к памяти и использования кэша алгоритма; [1] какой порядок лучше, также зависит от того, хранятся ли матрицы в порядке по строкам, по столбцам или в их сочетании.
В частности, в идеализированном случае полностью ассоциативного кэша, состоящего из M байтов и b байтов на строку кэша (т.е. М/б строк кэша), приведенный выше алгоритм неоптимален для A и B , хранящихся в порядке строк. Когда n > М/б каждая итерация внутреннего цикла (одновременный просмотр строки A и столбца B ) вызывает промах в кэше при доступе кэлементу B. Это означает, что в худшем случаеалгоритм допускает Θ( n 3 ) промахов в кэше. По состоянию на 2010 год[обновлять]скорость памяти по сравнению со скоростью процессоров такова, что промахи в кэше, а не фактические вычисления, доминируют во времени работы для матриц значительного размера. [7]
Оптимальным вариантом итерационного алгоритма для A и B в разбивке по строкам является мозаичная версия, в которой матрица неявно делится на квадратные плитки размером √ M на √ M : [7] [8]
В идеализированной модели кэша этот алгоритм требует только Θ( № 3/б √ М ) промахи в кэше; делитель b √ M на современных машинах составляет несколько порядков, так что фактические вычисления доминируют во времени выполнения, а не промахи в кэше. [7]
Альтернативой итеративному алгоритму является алгоритм умножения матриц « разделяй и властвуй» . Это зависит от разделения блоков
который работает для всех квадратных матриц, размеры которых являются степенями двойки, т. е. формы имеют размер 2 n × 2 n для некоторого n . Матричный продукт теперь
который состоит из восьми умножений пар подматриц с последующим шагом сложения. Алгоритм «разделяй и властвуй» рекурсивно вычисляет меньшие умножения , используя скалярное умножение c 11 = a 11 b 11 в качестве базового случая.
Сложность этого алгоритма как функция от n определяется рекуррентностью [6]
с учетом восьми рекурсивных вызовов матриц размера n /2 и Θ( n2 ) для поэлементного суммирования четырех пар результирующих матриц. Применение основной теоремы для повторений по принципу «разделяй и властвуй» показывает, что эта рекурсия имеет решение Θ( n 3 ) , такое же, как и итерационный алгоритм. [6]
Вариант этого алгоритма, который работает для матриц произвольной формы и на практике работает быстрее [7], разбивает матрицы на две вместо четырех подматриц следующим образом. [9] Разделение матрицы теперь означает разделение ее на две части одинакового размера или как можно ближе к равным размерам в случае нечетных размеров.
Частота промахов в кэше при рекурсивном умножении матриц такая же, как и в мозаичной итеративной версии, но, в отличие от этого алгоритма, рекурсивный алгоритм не учитывает кэш : [9] для получения оптимальной производительности кэша не требуется параметр настройки, и он ведет себя хорошо в мультипрограммной среде, где размеры кэша фактически динамичны из-за того, что другие процессы занимают пространство кэша. [7] (Простой итерационный алгоритм также не учитывает кэш, но на практике он работает намного медленнее, если расположение матрицы не адаптировано к алгоритму.)
Количество промахов кэша, возникающих в результате этого алгоритма, на машине с M строками идеального кэша, каждая размером b байт, ограничено [9] : 13.
Существуют алгоритмы, которые обеспечивают лучшее время работы, чем простые алгоритмы. Первым был открыт алгоритм Штрассена , разработанный Фолькером Штрассеном в 1969 году и часто называемый «быстрым умножением матриц». Он основан на способе умножения двух матриц размера 2 × 2 , который требует всего 7 умножений (вместо обычных 8) за счет нескольких дополнительных операций сложения и вычитания. Рекурсивное применение этого метода дает алгоритм с мультипликативной стоимостью . Алгоритм Штрассена более сложен, и численная стабильность снижается по сравнению с наивным алгоритмом [10] , но он быстрее в случаях, когда n > 100 или около того [1] и появляется в нескольких библиотеках, таких как BLAS . [11] Это очень полезно для больших матриц в точных областях, таких как конечные поля , где численная стабильность не является проблемой.
Поскольку алгоритм Штрассена фактически используется в практическом численном программном обеспечении и системах компьютерной алгебры, улучшение констант, скрытых в большой нотации O, имеет свои преимущества. Ниже приведена таблица, в которой сравниваются ключевые аспекты улучшенной версии, основанной на рекурсивном умножении блочных матриц 2x2 посредством умножения 7-блочных матриц. Как обычно дает размеры матрицы и обозначает объем памяти.
Известно, что алгоритм Штрассена с шагом блочной матрицы 2x2 требует не менее 7 умножений блочной матрицы. В 1976 году Проберт [15] показал, что такой алгоритм требует не менее 15 сложений (включая вычитания), однако скрытым предположением было то, что блоки и блочная матрица 2x2 представлены в одном и том же базисе. Карштадт и Шварц произвели вычисления в разных базисах и обменяли 3 сложения на менее затратные преобразования базисов. Они также доказали, что при использовании разных базисов нельзя опускаться ниже 12 сложений за шаг. В последующей работе Бениамини и др. [16] применили этот трюк с изменением базы к более общим разложениям, чем блочные матрицы 2x2, и улучшили ведущую константу для их времени выполнения.
В теоретической информатике остается открытым вопрос, насколько хорошо алгоритм Штрассена можно улучшить с точки зрения асимптотической сложности . Показатель умножения матрицы , обычно обозначаемый , представляет собой наименьшее действительное число, для которого любая матрица над полем может быть перемножена с помощью операций с полем. На данный момент лучшими являются Уильямс , Сюй , Сюй и Чжоу. [2] [4] Этот алгоритм, как и все другие недавние алгоритмы в этом направлении исследований, является обобщением алгоритма Копперсмита-Винограда, который был предложен Доном Копперсмитом и Шмуэлем Виноградом в 1990 году. [17] Концептуальная идея этих алгоритмов. Алгоритмы аналогичны алгоритму Штрассена: разработан способ умножения двух k × k -матриц с числом умножений менее k 3 , и этот метод применяется рекурсивно. Однако постоянный коэффициент, скрытый обозначением Big O, настолько велик, что эти алгоритмы имеют смысл только для матриц, которые слишком велики для обработки на современных компьютерах. [18] [19]
Алгоритм Фрейвалдса — это простой алгоритм Монте - Карло , который по матрицам A , B и C проверяет за время Θ( n2 ) , если AB = C.
В 2022 году DeepMind представила AlphaTensor — нейронную сеть , которая использовала аналогию с однопользовательской игрой для изобретения тысяч алгоритмов умножения матриц, включая некоторые из них, ранее обнаруженные людьми, а некоторые — нет. [20] Операции были ограничены некоммутативным основным полем (нормальная арифметика) и конечным полем (арифметика по модулю 2). Лучший найденный «практический» алгоритм (явное разложение тензора матричного умножения низкого ранга) работал за O(n 2,778 ). [21] Нахождение разложений низкого ранга таких тензоров (и не только) NP-трудно; оптимальное умножение даже для матриц 3х3 остается неизвестным даже в коммутативном поле. [21] Для матриц 4x4 компания AlphaTensor неожиданно обнаружила решение с 47 шагами умножения, что является улучшением по сравнению с 49 шагами, необходимыми для алгоритма Штрассена 1969 года, хотя и ограниченным арифметикой по модулю 2. Аналогично, AlphaTensor решал матрицы 5x5 за 96 шагов вместо 98 шагов Штрассена. Основываясь на неожиданном открытии существования таких улучшений, другие исследователи быстро смогли найти аналогичный независимый алгоритм 4x4 и отдельно доработали 96-шаговый алгоритм Deepmind 5x5 до 95 шагов в арифметике по модулю 2 и до 97 [22] в обычной арифметике. [23] Некоторые алгоритмы никогда раньше не были обнаружены, например, (4, 5, 5) был улучшен до 76 с 80 в обычной арифметике и арифметике по модулю 2.
Описанный ранее алгоритм «разделяй и властвуй» можно распараллелить двумя способами для мультипроцессоров с общей памятью . Они основаны на том факте, что восемь рекурсивных матричных умножений в
могут выполняться независимо друг от друга, как и четыре суммирования (хотя алгоритму необходимо «объединить» умножения перед выполнением суммирования). Используя полный параллелизм задачи, можно получить алгоритм, который можно выразить в псевдокоде в стиле разветвления-объединения : [24]
Процедура умножения( C , A , B ) :
Процедура add( C , T ) добавляет T в C поэлементно:
Здесь fork — это ключевое слово, которое сигнализирует, что вычисление может выполняться параллельно с остальной частью вызова функции, в то время как соединение ожидает завершения всех ранее «разветвленных» вычислений. Раздел достигает своей цели только за счет манипулирования указателями.
Длина критического пути этого алгоритма составляет Θ (log 2 n ) шагов, а это означает, что на идеальной машине с бесконечным числом процессоров он занимает столько же времени; следовательно, он имеет максимально возможное ускорение Θ ( n 3 /log 2 n ) на любом реальном компьютере. Алгоритм непрактичен из-за затрат на связь, связанных с перемещением данных во временную матрицу T и обратно , но более практичный вариант обеспечивает ускорение Θ( n 2 ) без использования временной матрицы. [24]
В современных архитектурах с иерархической памятью стоимость загрузки и хранения элементов входной матрицы обычно превышает стоимость арифметических операций. На одной машине это объем данных, передаваемых между ОЗУ и кешем, а на многоузловой машине с распределенной памятью - это объем, передаваемый между узлами; в любом случае это называется полосой пропускания связи . Наивный алгоритм, использующий три вложенных цикла, использует полосу пропускания связи Ω( n 3 ) .
Алгоритм Кэннона , также известный как 2D-алгоритм , представляет собой алгоритм, позволяющий избежать связи, который разбивает каждую входную матрицу на блочную матрицу, элементы которой являются подматрицами размера √ M /3 на √ M /3 , где M — размер быстрой памяти. [25] Затем наивный алгоритм применяется к блочным матрицам, вычисляя произведения подматриц полностью в быстрой памяти. Это уменьшает пропускную способность связи до O ( n3 / √M ) , что является асимптотически оптимальным (для алгоритмов, выполняющих вычисление Ω( n3 ) . [26] [27]
В распределенной среде с p процессорами, расположенными в двумерной сетке √ p на √ p , каждому процессору может быть назначена одна подматрица результата, и произведение может быть вычислено с помощью каждого процессора, передающего O ( n 2 / √ p ) слов, что является асимптотически оптимальным, если предположить, что каждый узел хранит минимум O ( n 2 / p ) элементов. [27] Это можно улучшить с помощью 3D-алгоритма, который размещает процессоры в сетке 3D-куба, назначая каждый продукт двух входных подматриц одному процессору. Затем генерируются результирующие подматрицы путем сокращения каждой строки. [28] Этот алгоритм передает O ( n 2 / p 2/3 ) слов на процессор, что является асимптотически оптимальным. [27] Однако это требует репликации каждого элемента входной матрицы p 1/3 раз, и поэтому требует в p 1/3 больше памяти, чем необходимо для хранения входных данных. Этот алгоритм можно комбинировать с алгоритмом Штрассена для дальнейшего сокращения времени выполнения. [28] Алгоритмы «2.5D» обеспечивают постоянный компромисс между использованием памяти и пропускной способностью связи. [29] В современных распределенных вычислительных средах, таких как MapReduce , были разработаны специализированные алгоритмы умножения. [30]
Существует множество алгоритмов умножения на сетках . Для умножения двух n × n на стандартной двумерной сетке с использованием алгоритма 2D Кэннона можно выполнить умножение за 3 n -2 шага, хотя при повторных вычислениях это число сокращается вдвое. [31] Стандартный массив неэффективен, поскольку данные из двух матриц не поступают одновременно, и их необходимо дополнять нулями.
Результат еще быстрее на двухслойной сетке с перекрестными проволоками, где требуется всего 2 n -1 шагов. [32] Производительность еще больше улучшается при повторных вычислениях, что приводит к 100% эффективности. [33] Массив с перекрестной сеткой можно рассматривать как частный случай неплоской (т.е. многослойной) структуры обработки. [34]
Алгоритм Копперсмита-Винограда: непрактично из-за очень большой скрытой константы в верхней границе необходимого количества умножений.
Даже если кому-то удастся доказать одну из гипотез, продемонстрировав тем самым, что
ω
= 2
— венок Продуктовый подход вряд ли будет применим к большим матричным проблемам, возникающим на практике. [...] входные матрицы должны быть астрономически большими, чтобы разница во времени была очевидна.