Ускорение матрицы 5x5 с плавающей запятой * векторное умножение с помощью SSE

Мне нужно запустить умножение матрицы на вектор 240000 раз в секунду. Матрица 5x5 всегда одна и та же, а вектор меняется на каждой итерации. Тип данных float. Я думал об использовании некоторых инструкций SSE (или подобных).

  1. Меня беспокоит, что количество арифметических операций слишком мало по сравнению с количеством задействованных операций с памятью. Как вы думаете, смогу ли я получить какое-то ощутимое (например, > 20%) улучшение?

  2. Нужен ли для этого компилятор Intel?

  3. Можете ли вы указать некоторые ссылки?


person Enzo    schedule 07.07.2011    source источник
comment
Публикация в качестве комментария, а не ответа, поскольку это всего лишь предположение, но разве некоторые компиляторы не оптимизируют различные операции умножения матриц? Кажется, я помню старый университетский проект умножения вложенных циклов for по сравнению с многопоточным умножением, который имел намного более быстрое время выполнения из-за оптимизации...   -  person Grambot    schedule 08.07.2011
comment
Если вы написали какой-либо код, пожалуйста, напишите. Сколько раз ужасно? Сколько времени это займет сегодня, и чего бы вы хотели достичь?   -  person Fredrik Pihl    schedule 08.07.2011
comment
msdn.microsoft.com/en-us/ библиотека/y0dh78ez%28v=vs.80%29.aspx   -  person OrangeDog    schedule 08.07.2011
comment
Вы уже используете BLAS?   -  person leftaroundabout    schedule 08.07.2011
comment
Каков тип данных в матрице и векторе? Одинарная точность? Двойная точность ? Целое число?   -  person Paul R    schedule 08.07.2011
comment
Также должно ли это работать практически на любом процессоре x86, или мы можем предположить, например. Intel и SSSE3 или более поздняя версия?   -  person Paul R    schedule 08.07.2011
comment
SSE ничего не сделает, если у вас постоянно отсутствует кеш, что неизбежно в случае с матрицами 5x5 (одна из матриц имеет плохой порядок, а 5 — плохое число: вы будете кэшировать промах каждой итерации цикла). Используйте Intel IPP и даже не пытайтесь сделать это самостоятельно.   -  person Alexandre C.    schedule 08.07.2011
comment
@ Александр С.: матрицы? Множественное число? Вопрос говорит всегда одно и то же. Кроме того, 5*5*sizeof(double) намного меньше размера даже кеша L1. Почему вы получаете промахи кеша?   -  person MSalters    schedule 08.07.2011


Ответы (8)


Библиотека шаблонов C++ Eigen для векторов, матриц и т. д.

  • оптимизированный код для небольших матриц фиксированного размера (а также динамических)

  • оптимизированный код, использующий оптимизацию SSE

так что вы должны дать ему попробовать.

person Dirk Eddelbuettel    schedule 07.07.2011
comment
Обратите внимание, что документы Eigen утверждают, что он плохо работает с фиксированными векторами с размером, не кратным 16 байтам, поэтому он не может автоматически векторизоваться для этой проблемы. Так ли это по-прежнему с Eigen3, я не могу сказать. - person John L; 08.07.2011
comment
Спасибо за это примечание - я не знал об этом ограничении. Но тогда я все равно больше использую векторы и матрицы с динамическими размерами. - person Dirk Eddelbuettel; 08.07.2011
comment
@John L Спасибо за ваш комментарий. Да, я нашел то же самое в документации. Как вы думаете, это из-за основного ограничения оптимизации SSE или библиотеки? Спасибо! - person Enzo; 08.07.2011
comment
@Enzo: Это о SSE. SSE выполняет X, обычно 4 флопа за одну инструкцию. Если вы не кратны 4 (* 4 байта с плавающей запятой = 16 байтов), вы не можете выразить операцию только в инструкциях SSE. - person Puppy; 08.07.2011
comment
@Enzo - DeadMG совершенно прав. Если Eigen не получится, попробуйте свернуть свой. Документы MSDN по встроенным функциям SSE довольно хороши, в основном они одинаковы для других компиляторов. - person John L; 10.07.2011
comment
@DeadMG, Enzo и т. д.: вы можете использовать инструкции SSE для размеров векторов/матриц, которые не кратны 128 битам, если размер = 128 бит. Как упомянул DeadMG, вам нужно разделить цикл на 3 части: «начальная» часть, которая использует чистые итерации С++ для выравнивания по 128-битной границе, «основная» часть, которая использует выровненные инструкции SSE для выполнения основной части работы. , и «последняя» часть, в которой используются чистые итерации С++ для выполнения всего остального. - person Darren Engwirda; 10.07.2011
comment
@everyone, как насчет заполнения нулями? Можно ли сделать с векторами то, что можно сделать с полиномами? - person rishta; 15.11.2012
comment
Тот факт, что матрица 5x5 не кратна четырем, является отвлекающим маневром. Вместо этого вы формируете матрицу 5x4 из векторов и умножаете на нее фиксированную матрицу 5x5. В принципе, вы можете получить 4-кратное ускорение с помощью SSE. Та же идея может быть использована для вектора любого размера (например, 3D-вектора). Смотрите мой ответ. - person ; 18.03.2013

В принципе ускорение может быть в 4 раза с SSE (в 8 раз с AVX). Позволь мне объяснить.

Назовем вашу фиксированную матрицу 5x5 M. Определение компонентов 5D-вектора как (x, y, z, w, t). Теперь сформируйте матрицу 5x4 U из первых четырех векторов.

U =
xxxx
yyyy
zzzz
wwww
tttt

Затем сделайте матричное произведение MU = V. Матрица V содержит произведение M и первых четырех векторов. Единственная проблема заключается в том, что для SSE нам нужно читать в строках U, но в памяти U хранится как xyzwtxyzwtxyzwtxyzwt, поэтому мы должны транспонировать его на xxxxyyyyzzzzwwwwtttt. Это можно сделать с помощью перемешивания/смешивания в SSE. Когда у нас есть этот формат, матричный продукт становится очень эффективным.

Вместо того, чтобы выполнять операции O (5x5x4) со скалярным кодом, требуется только операции O (5x5), то есть ускорение в 4 раза. С AVX матрица U будет 5x8, поэтому вместо операций O(5x5x8) она облагает налогом только O(5x5), то есть ускорение в 8 раз.

Однако матрица V будет иметь формат xxxxyyyyzzzzwwwwtttt, поэтому в зависимости от приложения ее может потребоваться преобразовать в формат xyzwtxyzwtxyzwtxyzwt.

Повторите это для следующих четырех векторов (8 для AVX) и так далее, пока не закончите.

Если у вас есть контроль над векторами, например, если ваше приложение генерирует векторы на лету, вы можете генерировать их в формате xxxxyyyyzzzzwwwwtttt и избегать транспонирования массива. В этом случае вы должны получить 4-кратное ускорение с SSE и 8-кратное с AVX. Если вы объедините это с потоковой передачей, например. OpenMP, ваше ускорение должно быть близко к 16x (при условии четырех физических ядер) с SSE. Я думаю, что это лучшее, что вы можете сделать с SSE.

Редактировать: из-за параллелизма на уровне инструкций (ILP) вы можете получить еще один коэффициент ускорения 2, поэтому ускорение для SSE может быть 32-кратным с четырьмя ядрами (64x AVX) и снова еще одним коэффициентом 2 с Haswell из-за FMA3.

person Community    schedule 17.03.2013
comment
ILP и FMA также выиграют скалярно; это не уникально для SIMD. В этот момент вы просто вычисляете теоретическое максимальное значение FLOPS/такт, а не ускорение относительно скаляра. - person Peter Cordes; 04.01.2020

Я бы предложил использовать Intel IPP и абстрагироваться от зависимости от методов

person Ulterior    schedule 07.07.2011
comment
Они, вероятно, много знают о хитрых методах использования кэшей процессоров Intel. Вы должны сравнить с Eigen, хотя IPP лучше подходит для этого. - person Alexandre C.; 08.07.2011

Если вы используете GCC, обратите внимание, что параметр -O3 включает автоматическую векторизацию, которая во многих случаях автоматически генерирует инструкции SSE или AVX. В общем, если вы просто напишете это как простой цикл for, GCC векторизует его. См. http://gcc.gnu.org/projects/tree-ssa/vectorization.html для получения дополнительной информации.

person Jeremy Salwen    schedule 08.07.2011
comment
любой приличный компилятор может сделать автовекторизацию, но только для некоторого простого известного шаблона. В любом другом случае вам нужно будет написать векторизованный код самостоятельно или использовать библиотеку, написанную с учетом этого. - person phuclv; 03.01.2020

Это должно быть легко, особенно когда вы используете Core 2 или более позднюю версию: вам нужно 5* _mm_dp_ps , одно _mm_mul_ps, два _mm_add_ps, одно обычное умножение, а также несколько перетасовок, загрузок и сохранений (и если матрица фиксирована, вы можете сохранить большая часть в регистрах SSE, если они вам больше ни для чего не нужны).

Что касается пропускной способности памяти: мы говорим о 2,4 мегабайтах векторов, когда пропускная способность памяти выражается одноразрядными гигабайтами в секунду.

person maniek    schedule 07.07.2011

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

Классический метод оптимизации для обмена памяти на циклы...

person Fredrik Pihl    schedule 07.07.2011
comment
Мне кажется оптимистичным, что должно быть разумно ограниченное количество значений, которые может принимать вектор, но может не возникнуть проблемы с соответствующим квантованием векторов. Чтобы сделать это лучше, можно затем интерполировать между этими квантованными векторами и получить гораздо лучшие результаты, но это, вероятно, будет медленнее, чем правильно оптимизированное прямое матричное умножение. - person leftaroundabout; 08.07.2011
comment
@leftaroundabout - возможно, возможно, нет. OP должен собирать статистику по вводу, а затем решать, можно ли это использовать или нет. В предыдущем проекте я обнаружил, что более 95% вызовов очень сложной функции вычисления имели очень ограниченный диапазон, предварительный расчет которых ускорял работу на величину или больше. Если не найдено в таблице-справочнике, то мы прибегнем к расчету с нуля. - person Fredrik Pihl; 08.07.2011
comment
Спасибо за ответ! К сожалению, я не могу этого сделать. Это программное обеспечение реального времени, и количество возможных векторов бесконечно. - person Enzo; 08.07.2011

Я бы рекомендовал взглянуть на оптимизированную библиотеку BLAS, такую ​​как Intel MKL или AMD ACML. Основываясь на вашем описании, я бы предположил, что вы будете после матрично-векторной процедуры SGEMV уровня 2, чтобы выполнять операции в стиле y = A*x.

Если вы действительно хотите что-то реализовать самостоятельно, использование (доступных) наборов инструкций SSE..SSE4 и AVX может в некоторых случаях значительно повысить производительность, хотя именно это и будет делать хорошая библиотека BLAS. Вам также нужно много думать о шаблонах доступа к данным, дружественных к кэшу.

Я не знаю, применимо ли это в вашем случае, но можете ли вы работать с «кусками» векторов одновременно?? Таким образом, вместо повторного выполнения операции в стиле y = A*x вы можете работать с блоками [y1 y2 ... yn] = A * [x1 x2 ... xn]. Если это так, это означает, что вы можете использовать оптимизированную матричную подпрограмму, такую ​​как SGEMM. Из-за шаблонов доступа к данным это может быть значительно эффективнее повторных вызовов SGEMV. Если бы это был я, я бы попытался пойти по этому пути...

Надеюсь это поможет.

person Darren Engwirda    schedule 07.07.2011
comment
Я ожидаю, что фиксированная матрица 5x5 может храниться полностью в регистрах, поэтому доступ к кешу не будет иметь большого эффекта (при условии, что векторы имеют разумную компоновку). Из-за этого это кажется довольно хорошей задачей для введения в программирование SSE. Хотя это все равно было бы моим последним средством после того, как я попробовал параметры компилятора и библиотеки. - person John L; 10.07.2011
comment
@Джон Л: А?? Вам все еще нужно загрузить регистры, прежде чем вы сможете их использовать, и вы определенно хотите упорядочить свои инструкции, чтобы вы делали это непрерывно (возможно, даже с соответствующей предварительной выборкой данных). Это то, к чему я стремился с помощью шаблонов доступа к кешу... :) - person Darren Engwirda; 10.07.2011
comment
матрица не меняется, поэтому вам нужно загрузить ее только один раз, прежде чем начнутся итерации. Проблема ОП, вероятно, похожа на y[0] = i[0]; y[n] = m*(y[n-1]). На каждой итерации необходимо загружать только новый вектор, что большинство программистов будет делать непрерывно, и даже если нет, компилятор с гораздо большей вероятностью обнаружит его и изменит порядок. - person John L; 10.07.2011

Если вы знаете векторы заранее (например, выполняете все 240 КБ одновременно), вы получите лучшее ускорение, распараллелив цикл, чем переходя к SSE. Если вы уже сделали этот шаг или не знаете их всех сразу, SSE может быть большим преимуществом.

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

5x5 — забавный размер, но вы можете сделать по крайней мере 4 флопа в одной инструкции SSE и попытаться сократить свои арифметические накладные расходы. Вам не нужен компилятор Intel, но он может быть лучше, я слышал легенды о том, что он намного лучше с арифметическим кодом. В Visual Studio есть встроенные средства для работы с SSE2, и я думаю, что до SSE4, в зависимости от того, что вам нужно. Конечно, вам придется качать его самостоятельно. Захват библиотеки может быть разумным ходом здесь.

person Puppy    schedule 07.07.2011