Почему умножение матрицы Штрассена происходит медленно?

Я написал две программы умножения матриц на C++: Regular MM (источник), и Штрассен мм (источник), оба из которых работают на квадратных матрицах размеров 2^k x 2^k(другими словами, квадратные матрицы четного размера).

результаты просто ужасные. Для матрицы 1024 x 1024 обычный мм принимает 46.381 sec, в то время как ШТРАССЕН MM принимает 1484.303 sec (25 minutes !!!!).

Я попытался сохранить код как можно более простым. Другие примеры MM Штрассена найденные в Интернете не сильно отличаются от моего кода. Одна проблема с кодом Штрассен очевиден - у меня нет отсечки, что переключается в обычный мм.

какие еще проблемы имеет MM-код моего Штрассена ???

спасибо !

прямые ссылки на sources
http://pastebin.com/HqHtFpq9
http://pastebin.com/USRQ5tuy

Edit1. Кулак, много хороших советов. Спасибо, что уделили время и поделились знание.

я реализовал изменения (сохранил весь мой код), добавил точку отсечения. Мм матрицы 2048x2048, с отсечкой 512 уже дает хорошие результаты. Обычный мм: 191.49 s Мм Штрассена: 112.179 с Значительное улучшение. Результаты были получены на доисторическом Lenovo x61 TabletPC с процессором Intel Centrino, используя Visual Studio 2012. Я сделаю больше проверок (чтобы убедиться, что я получил правильные результаты) и опубликую результаты.

2 ответов


одна проблема с кодом Штрассена очевидна - у меня нет точки отсечения, что переключается в обычный мм

справедливо сказать, что рекурсия до 1 точки является основной (если не всей) проблемой. Попытка угадать другие узкие места производительности без решения этой проблемы почти спорна из-за массивного хита производительности, который он приносит. (Другими словами, вы сравниваете яблоки с апельсинами.)

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

void strassen(int **a, int **b, int **c, int tam) {

    // trivial case: when the matrix is 1 X 1:
    if (tam == 1) {
            c[0][0] = a[0][0] * b[0][0];
            return;
    }

это слишком мало. Хотя алгоритм Штрассена имеет меньшую сложность, он имеет гораздо большую константу Big-O. Во-первых, у вас есть накладные расходы на вызов функции вплоть до 1 элемента.

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

в быстрой сортировке / слиянии вы вернетесь к низким накладным расходам O(n^2) вставка или сортировка выбора. Здесь вы бы вернулись к нормальному O(n^3) матрицы умножать.


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

для чего-то вроде умножения Штрассена, где преимущество только O(2.8074) по сравнению с классическим O(n^3), Не удивляйтесь, если этот порог окажется очень высоким. (тысячи элементов?)


в некоторых приложениях может быть много алгоритмов с уменьшающейся сложностью, но увеличивающейся большой-O. В результате несколько алгоритмов становятся оптимальными при разных размерах.

большое целочисленное умножение является пресловутым пример:

*обратите внимание, что эти примеры порогов являются приблизительными и могут сильно отличаться - часто более чем в 10 раз.


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

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

Edit: это, вероятно, только вносит небольшой вклад в проблему. Проблема, скорее всего, одна Лукиан Григоре относится к участием проблемы с кеш-линией с полномочиями двух.

Я проверил, что мое беспокойство справедливо для наивного алгоритма. Время для наивного алгоритма уменьшается почти на 50%, если массив является непрерывным. Вот код для этого (используя класс SquareMatrix, который C++11 зависимый) от pastebin.