Умножение матриц язык си + видео обзор

Содержание
  1. Пошаговое руководство. Умножение матриц Walkthrough: Matrix Multiplication
  2. Предварительные требования Prerequisites
  3. Создание проекта To create the project
  4. Создание проекта в Visual Studio 2019 To create the project in Visual Studio 2019
  5. Создание проекта в Visual Studio 2017 или 2015 To create a project in Visual Studio 2017 or 2015
  6. Умножение без мозаичного заполнения Multiplication without tiling
  7. Умножение без использования C++ AMP To multiply without using C++ AMP
  8. Умножение с помощью C++ AMP To multiply by using C++ AMP
  9. Умножение с помощью мозаичного заполнения Multiplication with tiling
  10. Умножение с помощью AMP и мозаичного заполнения To multiply by using AMP and tiling
  11. Умножение квадратных матриц
  12. Решение
  13. Решение
  14. Умножение матриц: эффективная реализация шаг за шагом
  15. Введение
  16. Постановка задачи (0-й шаг)
  17. Устраняем очевидные недостатки (1-й шаг)
  18. Векторизуем внутренний цикл (2-й шаг)
  19. Пишем микроядро (3-й шаг)
  20. Переупорядочиваем матрицу B (4-й шаг)
  21. Локализуем данные в кэше L1 (5-й шаг)
  22. Переупорядочиваем матрицу A и локализуем в кэше L2 (6-й шаг)
  23. Задействуем кэш L3 (7-й шаг)
  24. Общая схема алгоритма
  25. Микро ядро
  26. Макро ядро
  27. Основная функция
  28. Что осталось за кадром?
  29. Заключение
  30. Видео

Пошаговое руководство. Умножение матриц Walkthrough: Matrix Multiplication

В этом пошаговом руководстве показано, как использовать C++ AMP для ускорения выполнения умножения матрицы. This step-by-step walkthrough demonstrates how to use C++ AMP to accelerate the execution of matrix multiplication. Представлены два алгоритма: один — без мозаичного заполнения и один с мозаичным заполнением. Two algorithms are presented, one without tiling and one with tiling.

Предварительные требования Prerequisites

Перед началом работы Before you start:

Убедитесь, что используется как минимум Windows 7 или Windows Server 2008 R2. Make sure that you are running at least Windows 7, or Windows Server 2008 R2.

Создание проекта To create the project

Инструкции по созданию нового проекта зависят от установленной версии Visual Studio. Instructions for creating a new project vary depending on which version of Visual Studio you have installed. Чтобы ознакомиться с документацией по предпочтительной версии Visual Studio, используйте селектор Версия. To see the documentation for your preferred version of Visual Studio, use the Version selector control. Он находится в верхней части оглавления на этой странице. It’s found at the top of the table of contents on this page.

Создание проекта в Visual Studio 2019 To create the project in Visual Studio 2019

В строке меню выберите Файл > Создать > Проект, чтобы открыть диалоговое окно Создание проекта. On the menu bar, choose File > New > Project to open the Create a New Project dialog box.

Умножение матриц язык си Умножение матриц язык си

Нажмите кнопку Создать, чтобы создать клиентский проект. Choose the Create button to create the client project.

В Обозреватель решений откройте контекстное меню исходных файлов и выберите команду добавить > новый элемент. In Solution Explorer, open the shortcut menu for Source Files, and then choose Add > New Item.

Создание проекта в Visual Studio 2017 или 2015 To create a project in Visual Studio 2017 or 2015

В строке меню Visual Studio выберите файл > создать > проект. On the menu bar in Visual Studio, choose File > New > Project.

В разделе установленные на панели шаблоны выберите Visual C++. Under Installed in the templates pane, select Visual C++.

Нажмите кнопку Далее. Choose the Next button.

В Обозреватель решений откройте контекстное меню исходных файлов и выберите команду добавить > новый элемент. In Solution Explorer, open the shortcut menu for Source Files, and then choose Add > New Item.

Умножение без мозаичного заполнения Multiplication without tiling

В этом разделе мы рассмотрим умножение двух матриц, A и B, которые определены следующим образом: In this section, consider the multiplication of two matrices, A and B, which are defined as follows:

Умножение матриц язык си Умножение матриц язык си

Умножение матриц язык си Умножение матриц язык си

A — это матрица размером 3 на 2, а B — матрица размером 2 на 3. A is a 3-by-2 matrix and B is a 2-by-3 matrix. Результатом умножения A на B является следующая матрица с 3 по 3. The product of multiplying A by B is the following 3-by-3 matrix. Продукт вычисляется путем умножения строк объекта на столбцы элемента B по элементу. The product is calculated by multiplying the rows of A by the columns of B element by element.

Умножение матриц язык си Умножение матриц язык си

Умножение без использования C++ AMP To multiply without using C++ AMP

Откройте Матриксмултипли. cpp и используйте следующий код, чтобы заменить существующий код. Open MatrixMultiply.cpp and use the following code to replace the existing code.

Алгоритм является простой реализацией определения умножения матрицы. The algorithm is a straightforward implementation of the definition of matrix multiplication. Он не использует параллельные или потоки алгоритмы для сокращения времени вычисления. It does not use any parallel or threaded algorithms to reduce the computation time.

В строке меню выберите Файл > Сохранить все. On the menu bar, choose File > Save All.

Умножение с помощью C++ AMP To multiply by using C++ AMP

В Матриксмултипли. cpp добавьте следующий код перед main методом. In MatrixMultiply.cpp, add the following code before the main method.

Добавьте следующие include инструкции и using в начало матриксмултипли. cpp. Add the following include and using statements at the top of MatrixMultiply.cpp.

Измените main метод, чтобы вызвать MultiplyWithAMP метод. Modify the main method to call the MultiplyWithAMP method.

Умножение с помощью мозаичного заполнения Multiplication with tiling

Мозаичное заполнение — это методика разделения данных на подмножества одинакового размера, которые называются плитками. Tiling is a technique in which you partition data into equal-sized subsets, which are known as tiles. При использовании мозаичного заполнения изменились три вещи. Three things change when you use tiling.

Можно создавать tile_static переменные. You can create tile_static variables. Доступ к данным в tile_static пространстве может выполняться в несколько раз быстрее, чем доступ к данным в глобальном пространстве. Access to data in tile_static space can be many times faster than access to data in the global space. Экземпляр tile_static переменной создается для каждой плитки, а все потоки в плитке имеют доступ к переменной. An instance of a tile_static variable is created for each tile, and all threads in the tile have access to the variable. Основным преимуществом мозаичного заполнения является повышение производительности из-за tile_static доступа. The primary benefit of tiling is the performance gain due to tile_static access.

У вас есть доступ к индексу потока относительно всего array_view объекта и индекса, относящихся к плитке. You have access to the index of the thread relative to the entire array_view object and the index relative to the tile. С помощью локального индекса можно упростить чтение и отладку кода. By using the local index, you can make your code easier to read and debug.

Чтобы воспользоваться преимуществами мозаичного умножения матрицы, алгоритм должен секционировать матрицу на плитках, а затем копировать данные плитки в tile_static переменные для ускорения доступа. To take advantage of tiling in matrix multiplication, the algorithm must partition the matrix into tiles and then copy the tile data into tile_static variables for faster access. В этом примере матрица секционирована на подматрицы одинакового размера. In this example, the matrix is partitioned into submatrices of equal size. Продукт можно найти, умножив подматрицы. The product is found by multiplying the submatrices. В этом примере используются две матрицы и их продукты: The two matrices and their product in this example are:

Умножение матриц язык си Умножение матриц язык си

Умножение матриц язык си Умножение матриц язык си

Умножение матриц язык си Умножение матриц язык си

Матрицы разделены на четыре матрицы 2×2, которые определяются следующим образом: The matrices are partitioned into four 2×2 matrices, which are defined as follows:

Умножение матриц язык си Умножение матриц язык си

Умножение матриц язык си Умножение матриц язык си

Теперь продукт A и B можно записать и вычислить следующим образом: The product of A and B can now be written and calculated as follows:

Умножение матриц язык си Умножение матриц язык си

Для реализации этого алгоритма код: To implement this algorithm, the code:

Использует tiled_extent объект вместо extent объекта в parallel_for_each вызове. Uses a tiled_extent object instead of an extent object in the parallel_for_each call.

Использует tiled_index объект вместо index объекта в parallel_for_each вызове. Uses a tiled_index object instead of an index object in the parallel_for_each call.

Создает tile_static переменные для хранения подматриц. Creates tile_static variables to hold the submatrices.

Использует tile_barrier::wait метод, чтобы прерывать потоки для вычисления продуктов подматриц. Uses the tile_barrier::wait method to stop the threads for the calculation of the products of the submatrices.

Умножение с помощью AMP и мозаичного заполнения To multiply by using AMP and tiling

В Матриксмултипли. cpp добавьте следующий код перед main методом. In MatrixMultiply.cpp, add the following code before the main method.

Этот пример значительно отличается от примера без мозаичного заполнения. This example is significantly different than the example without tiling. В коде используются следующие концептуальные шаги: The code uses these conceptual steps:

Умножение плитки [0, 0] завершено. The multiplication of tile[0,0] is complete.

Повторите эти четыре плитки. Repeat for the other four tiles. Индексирование для плиток не предусмотрено, и потоки могут выполняться в любом порядке. There is no indexing specifically for the tiles and the threads can execute in any order. По мере выполнения каждого потока tile_static переменные создаются для каждой плитки соответствующим образом и вызывают tile_barrier::wait Управление потоком программы. As each thread executes, the tile_static variables are created for each tile appropriately and the call to tile_barrier::wait controls the program flow.

При тщательном анализе алгоритма Обратите внимание, что каждая подматрица загружается в tile_static память дважды. As you examine the algorithm closely, notice that each submatrix is loaded into a tile_static memory twice. Эта перенаправление данных занимает некоторое время. That data transfer does take time. Однако после того, как данные находятся в tile_static памяти, доступ к данным будет выполняться гораздо быстрее. However, once the data is in tile_static memory, access to the data is much faster. Поскольку вычисление продуктов требует многократного доступа к значениям в подматрицах, существует общий выигрыш в производительности. Because calculating the products requires repeated access to the values in the submatrices, there is an overall performance gain. Для каждого алгоритма требуется экспериментирование, чтобы найти оптимальный алгоритм и размер плитки. For each algorithm, experimentation is required to find the optimal algorithm and tile size.

В примерах, отличных от AMP и не являющихся плитками, доступ к каждому элементу A и B осуществляется четыре раза из глобальной памяти для вычисления продукта. In the non-AMP and non-tile examples, each element of A and B is accessed four times from the global memory to calculate the product. В примере плитки доступ к каждому элементу осуществляется дважды из глобальной памяти и четыре раза из tile_static памяти. In the tile example, each element is accessed twice from the global memory and four times from the tile_static memory. Это не является значительной приростом производительности. That is not a significant performance gain. Однако если A и B были 1024×1024 матрицы и размер плитки был 16, то производительность будет значительно увеличена. However, if the A and B were 1024×1024 matrices and the tile size were 16, there would be a significant performance gain. В этом случае каждый элемент копируется в tile_static память 16 раз и доступ к нему осуществляется из tile_static памяти 1024 раз. In that case, each element would be copied into tile_static memory only 16 times and accessed from tile_static memory 1024 times.

Измените метод Main, чтобы вызвать MultiplyWithTiling метод, как показано ниже. Modify the main method to call the MultiplyWithTiling method, as shown.

Источник

Умножение квадратных матриц

Доброго времени суток.
Я опять прошу Вашей неоценимой помощи.
Столкнулся с задачей, нужно умножить 2-е квадратные матрицы.

У меня такой бред получился, что даже стыдно код сюда выкладывать(
Работаю под Линуксом, поэтому виндовые библиотеки не работают

Матрицы генерирую так:

Дан массив квадратных матриц. Определить, найдется ли в этом массиве пара взаимно обратных матриц
Дан массив квадратных матриц. Определить, найдется ли в этом массиве пара взаимно обратных матриц.

Умножение матриц (не работает для неквадратных матриц)
Доброго времени суток. Написал код для перемножения двух матриц. При вводе квадратной матрицы всё.

Умножение матриц Си
моя програма считает умножение двух матриц. Вводим одно Число(оно же будет размером двух матриц).

Параллельное умножение матриц
Всем привет, помогите написать программу) нужно написать программу Параллельное умножение матриц.

Решение

Спасибо, но сказано, что при умножении матриц 2х2 получается матрица 4х4.

По принципу умножения матрицы на число

Добавлено через 1 час 56 минут
Не подскажете хотя бы алгоритм?

что-то не вижу этого в стартовом сообщении.

ты точно в задание вчитался?

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

Добавлено через 2 минуты

Решение

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

Просто приведи пример перемножения двух конкретных матриц, а то я не могу понять, что же именно в моем коде не так

Результат
2 10 3 15
4 6 6 9
1 5 4 20
2 3 8 12

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

Умножение двух матриц
Здравствуйте, завтра нужно сдать лабу по сишке, но функция Multiplication отказывается работать.

Умножение матриц язык сиУмножение неквадратных матриц
Здравствуйте. Столкнулась с segmentation fault (core dumped) при умножении матриц. Сами они.

Умножение матриц язык сиУмножение матриц по Винограду
Для вариантов, предусматривающих решение систем линейных уравнений, умножение матриц и вычисление.

Источник

Умножение матриц: эффективная реализация шаг за шагом

Умножение матриц язык си

Введение

Умножение матриц — это один из базовых алгоритмов, который широко применяется в различных численных методах, и в частности в алгоритмах машинного обучения. Многие реализации прямого и обратного распространения сигнала в сверточных слоях неронной сети базируются на этой операции. Так порой до 90-95% всего времени, затрачиваемого на машинное обучение, приходится именно на эту операцию. Почему так происходит? Ответ кроется в очень эффективной реализации этого алгоритма для процессоров, графических ускорителей (а в последнее время и специальных ускорителей матричного умножения). Матричное умножение — один из немногих алгоритмов, которые позволяет эффективно задействовать все вычислительные ресурсы современных процессоров и графических ускорителей. Поэтому не удивительно, что многие алгоритмы стараются свести к матричному умножению — дополнительная расходы, связанные с подготовкой данных, как правило с лихвой окупаются общим ускорением алгоритмов.

Так как реализован алгоритм матричного умножения? Хотя сейчас существуют множество реализаций данного алгоритма, в том числе и в открытых исходных кодах. Но к сожалению, код данных реализаций (большей частью на ассемблере) весьма сложен. Существует хорошая англоязычная статья, подробно описывающая эти алгоритмы. К моему удивлению, я не обнаружил аналогов на Хабре. Как по мне, этого повода вполне достаточно, чтобы написать собственную статью. С целью ограничить объем изложения, я ограничился описанием однопоточного алгоритма для обычных процессоров. Тема многопоточности и алгоритмов для графических ускорителей явно заслуживает отдельной статьи.

Процесс изложения будет вестись ввиде шагов с примерами по последовательному ускорению алгоритма. Я старался писать максимально упрощая задачу, но не более того. Надеюсь у меня получилось…

Постановка задачи (0-й шаг)

В общем случае функция матричного умножения описывается как:

Где матрица A имеет размер M х K, матрица B — K х N, и матрица C — M х N.

Умножение матриц язык си

Мы без ущерба для изложения, можем считать, что a = 0 и b = 1:

Ее реализация на С++ «в лоб» по формуле будет выглядеть следующим образом:

120 GFLOPS (float-32) если ограничится использованием AVX2/FMA и

200 GFLOPS при использовании AVX-512). Так, что нужно предпринять, чтобы приблизится к теоретическому пределу? Далее мы в ходе ряда последовательных оптимизаций придем к решению, которое во многом воспроизводит то, что используется во многих стандартных библиотеках. В процессе оптимизации, я буду задействовать только AVX2/FMA, AVX-512 я касаться не буду, так как их распостраненность пока невелика.

Устраняем очевидные недостатки (1-й шаг)

Сначала устраним самые очевидные недостатки алгоритма:

Результат тестовых замеров показывает время выполнения в 250 мс, или 11.4 GFLOPS. Т.е. такими небольшими правками мы получили ускорение в 8 раз!

Векторизуем внутренний цикл (2-й шаг)

Запускаем тесты, получаем время 217 мс или 13.1 GFLOPS. Упс! Ускорение всего на 15%. Какже так? Тут нужно учитывать, два фактора:

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

Пишем микроядро (3-й шаг)

В предыдущей версии на 1 FMA операцию приходится 2 загрузки и 1 выгрузка.
Больше всего загрузок и выгрузок происходит с результирующей матрицей С: данные из нее нужно загрузить, прибавить к ним произведение C[i][j] += A[i][k]*B[k][j], а потом сохранить. И так много раз. Наиболее быстрая память, с которой может работать процессор — это его собственные регистры. Если мы будем хранить результирующее значение матрицы С в регистре процессора, то в процессе расчета нужно будет подгружать только значение матриц A и B. Теперь у нас на 1 FMA операцию приходится только 2 загрузки.

Если мы будем хранить в регистрах значения двух соседних столбцов матрицы C[i][j] и C[i][j+1], то сможем повторно использовать загруженное значение матрицы A[i][k]. И на 1 FMA операцию потребуется только 1.5 загрузки. Кроме того, сохраняя результат в 2 независимых регистра, мы позволим процессору выполнять 2 FMA операции за такт. Аналогично можно хранить в регистрах значения двух соседних строк — тогда будет осуществляться экономия на загрузке значений матрицы B.

Умножение матриц язык си

Всего настольные процессоры Интел начиная с 2-го поколения имеют 16 256-bit векторных регистров (справедливо для 64-bit режима процессора). 12 из них можно использовать для хранения кусочка результирующей матрицы С размером 6×16. В итоге мы сможем выполнить 12*8 = 96 FMA операций загрузив из памяти только 16 + 6 = 22 значений. И того нам удалось сократить доступ к памяти с 2.0 до 0.23 загрузки на 1 FMA операцию — почти в 10 раз!

Функция которая осуществляет вычисление такого маленького кусочка матрицы С, обычно называется микроядром, ниже приведен пример такой функции:

Введем небольшую вспомогательную функцию для инициализации начального значения матрицы С:

Здесь lda, ldb, ldc — длина строчки (Leading Dimension в общем случае) соответсвующей матрицы.

Тогда функция умножения примет следующий вид:

Запускаем ее и получаем время исполнения 78.5 мс или 36.2 GFLOPS. Т.е. использование микроядра позволило ускорить матричное умножение почти в 3 раза. Но полученное быстродействие все еще далеко от максимального. Где теперь узкое место?

Переупорядочиваем матрицу B (4-й шаг)

Микроядро за каждую итерацию загружает два 256-bit вектора из матрицы B.

Умножение матриц язык си

Причем каждый раз из новой строчки. Это делает невозможным для процессора эффективное кеширование этих данных. Для исправления этой ситуации сделаем два изменения:

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

Функция переупорядочивания матрицы B:

Ну и собственно 4-я версия функции gemm:

Результаты тестирования (29.5 мс или 96.5 GFLOPS) показывают, что мы на правильном пути. Фактически достигнуто около 80% от теоретически возможного максимума.

Победа? К сожалению нет. Просто размер матриц, который мы использовали для тестирования (M=N=K=1152) оказался удобным для данной версии алгоритма. Если увеличить К в 100 раз (M=1152, N=1152, K=115200), то эффективность алгоритма упадет до 39.5 GFLOPS — почти в 2.5 раза.

Локализуем данные в кэше L1 (5-й шаг)

Так почему же с ростом параметра K, падает эффективность алгоритма? Ответ кроется в величине буфера, который мы использовали для хранения переупорядоченных значений B. При больших значениях K он просто не влазит в кэш процессора. Решением проблемы будет ограничение его величины до размера кэша данных L1. Для процессоров Интел размер кэша данных L1 составляет 32 kb. C ограничением размера буфера, микроядро будет пробегать не по всем значениям K, а только по диапазону, который влазит в L1 кэш. Результаты промежуточных расчетов матрицы С будут храниться в основной памяти.

Введем макроядро — вспомогательную функцию, которая производит расчеты над областью данных, которые влазят в кэш:

В главной функции у нас добавится цикл по K, в котором мы будем вызывать макроядро:

Результаты замеров показывают, что мы движемся в правильном направлении: для (M=1152, N=1152, K=115200) производительность алгоритма составила 78.1 GFLOPS. Это значительно лучше, чем в прошлой версии, но все еще хуже, чем для матрицы средних размеров.

Переупорядочиваем матрицу A и локализуем в кэше L2 (6-й шаг)

Ограничив размер K, который обрабатывается за один проход микроядра, мы сумели локализовать данные матрицы B в кэше L1. Данных, которые подгружаются из матрицы A почти в три раза меньше. Но давайте попробуем локализовать и их, заодно переупорядочив данные, чтобы они лежали последовательно. Напишем для этого специальную функцию:

Так как, данные матрицы A теперь идут последовательно, то параметр lda в макроядре нам больше не нужен. Также поменялись параметры вызова микроядра:

Размер буфера для переупорядоченной матрицы A ограничиваем размером L2 кэша процессора (он обычно составляет от 256 до 1024 kb для разных типов процессоров). В главной функции добавляется дополнительный цикл по переменной M:

Результаты тестовых замеров для (M=1152, N=1152, K=115200) — 88.9 GFLOPS — приблизились еще на один шаг к результату для матриц среднего размера.

Задействуем кэш L3 (7-й шаг)

В процессорах помимо кэша L1 и L2 еще часто бывает кэш L3 (обычно его размер составляет 1-2 MB на ядро). Попробуем задействовать и его, например, для хранения переупорядоченных значений матриц B, чтобы избежать лишних вызовов функции reorder_b_16. В функции макроядра появится дополнительные параметр reorderB, который будет сообщать о том, что данныe матрицы B уже упорядочены:

В основной функции добавится цикл по N:

Результаты замеров для (M=1152, N=1152, K=115200) дают результат в 97.3 GFLOPS. Т.е. мы даже немного превысили результат для матриц среднего размера. Фактически мы получили универсальный алгоритм (на самом деле нет, про ограничения в следующем разделе), который практически одинаково эффективно (порядка 80% от теоретически достижимого макимума) работает для любого размера матриц. На этом предлагаю остановиться и описать, что у нас в итоге получилось.

Общая схема алгоритма

На рисунке ниже приведена схема получившегося алгоритма:

Умножение матриц язык си

Микро ядро

Макро ядро

Основная функция

Что осталось за кадром?

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

Заключение

Приведенный алгоритм матричного умножения позволяет эффективно задействовать ресурсы современных процессоров. Но он наглядно показывает, что максимальная утилизация ресурсов современных процессоров — это далеко нетривиальная задача. Подход с использованием микроядер и максимальной локализации данных в кэше процессора можно с успехом использовать и для других алгоритмов.

Код проекта с алгоритмами из статьи можно найти на Github.

Источник

Видео

Поделиться или сохранить к себе:
Технологии | AltArena.ru
Добавить комментарий

Нажимая на кнопку "Отправить комментарий", я даю согласие на обработку персональных данных, принимаю Политику конфиденциальности и условия Пользовательского соглашения.