Мне очень интересно оптимизировать решение линейных систем для маленьких матриц (10x10), которые иногда называют крошечными матрицами. Есть ли готовое решение для этого? Матрицу можно считать неособой.
Этот решатель должен выполняться более 1 000 000 раз в микросекундах на процессоре Intel. Я говорю об уровне оптимизации, используемом в компьютерных играх. Неважно, кодирую ли я его в сборке и для конкретной архитектуры, или изучаю снижение компромиссов между точностью и надежностью и использую хаки с плавающей запятой (я использую флаг компиляции -ffast-math, нет проблем). Решение может даже потерпеть неудачу примерно в 20% случаев!
Eigen'spartalPivLu является самым быстрым в моем текущем тесте, превосходя LAPACK, когда оптимизирован с -O3 и хорошим компилятором. Но сейчас я нахожусь на стадии ручного изготовления специального линейного решателя. Любой совет будет принята с благодарностью. Я сделаю свое решение открытым исходным кодом и узнаю ключевые идеи публикаций и т. Д.
Связанный: Скорость решения линейной системы с блочной диагональной матрицей Какой самый быстрый способ инвертировать миллионы матриц? https://stackoverflow.com/q/50909385/1489510
Ответы:
Использование собственного матричного типа, в котором число строк и столбцов кодируется в тип во время компиляции, дает преимущество над LAPACK, где размер матрицы известен только во время выполнения. Эта дополнительная информация позволяет компилятору выполнять полное или частичное развертывание цикла, устраняя множество инструкций ветвления. Если вы предпочитаете использовать существующую библиотеку, а не писать собственные ядра, возможно, важно иметь тип данных, в котором размер матрицы может быть включен в качестве параметров шаблона C ++. Единственная из известных мне библиотек, которая делает это, - пламя , так что, возможно, стоит сравнить с Эйгеном.
Если вы решите развернуть собственную реализацию, вы можете найти то, что PETSc делает для своего блочного CSR-формата, как полезный пример, хотя сам PETSc, вероятно, не будет подходящим инструментом для ваших целей. Вместо того, чтобы писать цикл, они записывают каждую отдельную операцию для небольших умножений матрицы на вектор (см. Этот файл в их хранилище). Это гарантирует, что нет никаких ветвящихся инструкций, которые вы могли бы получить с помощью цикла. Версии кода с инструкциями AVX являются хорошим примером того, как на самом деле использовать векторные расширения. Например, эта функция использует
__m256d
Тип данных для одновременной работы на четырех дублях. Вы можете получить заметное повышение производительности, явно записав все операции с использованием векторных расширений, только для факторизации LU вместо умножения матрицы на вектор. Вместо того, чтобы на самом деле писать код на C вручную, вам лучше использовать скрипт для его генерации. Также было бы интересно посмотреть, есть ли заметная разница в производительности, когда вы переупорядочиваете некоторые операции, чтобы лучше использовать конвейерную обработку команд.Вы также можете получить некоторое преимущество от инструмента STOKE , который будет случайным образом исследовать пространство возможных преобразований программы, чтобы найти более быструю версию.
источник
Другая идея может заключаться в использовании генеративного подхода (программа, пишущая программу). Автор (мета) программы, которая выплевывает последовательность инструкций C / C ++ для выполнения неотключенного ** LU в системе 10x10 .. в основном беря гнездо цикла k / i / j и выравнивая его в O (1000) или около того строк скалярная арифметика. Затем введите эту сгенерированную программу в любой оптимизирующий компилятор. Здесь я думаю, что это довольно интересно, так как удаление циклов раскрывает каждую зависимость от данных и избыточное подвыражение и дает компилятору максимальную возможность переупорядочить инструкции так, чтобы они хорошо отображались в реальном оборудовании (например, число исполнительных блоков, опасностей / остановок, поэтому на).
Если вы знаете все матрицы (или даже несколько из них), вы можете улучшить пропускную способность, вызывая встроенные функции / функции SIMD (SSE / AVX) вместо скалярного кода. Здесь вы будете использовать смущающий параллелизм между экземплярами, вместо того, чтобы гоняться за каким-либо параллелизмом внутри одного экземпляра. Например, вы можете выполнить 4 LU двойной точности одновременно, используя встроенные функции AVX256, упаковав 4 матрицы «поперек» регистра и выполнив одинаковые операции ** на всех из них.
** Отсюда акцент на непивотных LU. Поворот портит этот подход двумя способами. Во-первых, он вводит ответвления из-за выбора центра, что означает, что ваши зависимости от данных не так хорошо известны. Во-вторых, это означает, что разные «слоты» SIMD должны будут выполнять разные действия, потому что экземпляр A может вращаться не так, как экземпляр B. Поэтому, если вы решите что-то из этого, я бы предложил статически повернуть ваши матрицы перед вычислением (переставить наибольшую запись каждого столбца по диагонали).
источник
Ваш вопрос приводит к двум различным соображениям.
Во-первых, вам нужно выбрать правильный алгоритм. Следовательно, вопрос о том, имеют ли матрицы какую-либо структуру, должен быть рассмотрен. Например, когда матрицы симметричны, разложение Холецкого более эффективно, чем LU. Когда вам нужна только ограниченная точность, итерационный метод может быть быстрее.
Во-вторых, вам необходимо эффективно реализовать алгоритм. Для этого вам нужно знать узкое место вашего алгоритма. Ваша реализация ограничена скоростью передачи памяти или скоростью вычислений. Поскольку вы считаете только10 × 10 матрицы, ваша матрица должна полностью вписаться в кэш процессора. Таким образом, вы должны использовать блоки SIMD (SSE, AVX и т. Д.) И ядра вашего процессора, чтобы выполнять как можно больше вычислений за цикл.
В целом, ответ на ваш вопрос сильно зависит от оборудования и матриц, которые вы рассматриваете. Вероятно, нет однозначного ответа, и вам нужно попробовать несколько вещей, чтобы найти оптимальный метод.
источник
Я бы попробовал блочную инверсию.
https://en.wikipedia.org/wiki/Invertible_matrix#Blockwise_inversion
Eigen использует оптимизированную процедуру для вычисления обратной матрицы 4x4, что, вероятно, является лучшим, что вы получите. Попробуйте использовать это как можно больше.
http://www.eigen.tuxfamily.org/dox/Inverse__SSE_8h_source.html
Слева вверху: 8х8. Вверху справа: 8х2. Внизу слева: 2x8. Справа внизу: 2x2. Инвертируйте 8x8, используя оптимизированный код инверсии 4x4. Остальное - это матричные продукты.
РЕДАКТИРОВАТЬ: Использование блоков 6x6, 6x4, 4x6 и 4x4 оказалось немного быстрее, чем я описал выше.
Вот результаты одного теста с использованием миллиона
Eigen::Matrix<double,10,10>::Random()
матриц иEigen::Matrix<double,10,1>::Random()
векторов. Во всех моих тестах обратное всегда быстрее. Моя процедура решения включает в себя вычисление обратного и затем умножение его на вектор. Иногда это быстрее, чем Эйген, иногда нет. Мой метод разметки может быть ошибочным (не отключал турбо-буст и т. Д.). Кроме того, случайные функции Эйгена могут не представлять реальные данные.Мне очень интересно посмотреть, сможет ли кто-нибудь еще оптимизировать это, так как у меня есть приложение с конечными элементами, которое инвертирует матрицу gazillion 10x10 (и да, мне нужны индивидуальные коэффициенты обратного преобразования, так что непосредственное решение линейной системы не всегда возможно) ,
источник