Это может показаться очевидным, но компьютеры не выполняют формулы , они выполняют код , и время, которое занимает это выполнение, напрямую зависит от кода, который они выполняют, и только косвенно, от любой концепции, которую реализует этот код. Два логически идентичных фрагмента кода могут иметь очень разные характеристики производительности. Некоторые причины, которые могут возникнуть при умножении матриц, в частности:
- Использование нескольких потоков. Практически нет современных ЦП, которые бы не имели многоядерных процессоров, у многих - до 8, а на специализированных машинах для высокопроизводительных вычислений можно было бы легко использовать 64 на нескольких сокетах. Написание кода очевидным способом на обычном языке программирования использует только один из них. Другими словами, он может использовать менее 2% доступных вычислительных ресурсов компьютера, на котором он работает.
- Использование SIMD-инструкций (сбивает с толку, это также называется «векторизация», но в ином смысле, чем в текстовых кавычках в вопросе). По сути, вместо 4 или 8 или около того скалярных арифметических инструкций, дайте ЦПУ одну инструкцию, которая выполняет арифметику для 4 или 8 или около того регистров параллельно. Это может буквально сделать некоторые вычисления (когда они совершенно независимы и подходят для набора команд) в 4 или 8 раз быстрее.
- Разумнее использовать кеш . Доступ к памяти происходит быстрее, если они согласованы по времени и пространству , то есть последовательный доступ осуществляется к близлежащим адресам, и при двойном доступе к адресу вы получаете доступ к нему дважды в быстрой последовательности, а не с длинной паузой.
- Использование ускорителей, таких как графические процессоры. Эти устройства очень сильно отличаются от процессоров, и их эффективное программирование - это отдельная форма искусства. Например, они имеют сотни ядер, которые сгруппированы в группы из нескольких десятков ядер, и эти группы совместно используют ресурсы - они разделяют несколько килобайт памяти, что намного быстрее, чем обычная память, и когда любое ядро группы выполняет
if
Заявление все остальные в этой группе должны ждать его.
- Распределите работу по нескольким машинам (что очень важно для суперкомпьютеров!), Что создает огромный набор новых головных болей, но, конечно, может дать доступ к значительно большим вычислительным ресурсам
- Умные алгоритмы. Для умножения матриц простой алгоритм O (n ^ 3), должным образом оптимизированный с помощью вышеприведенных приемов, часто быстрее, чем субкубические при разумных размерах матриц, но иногда они выигрывают. Для особых случаев, таких как разреженные матрицы, вы можете написать специализированные алгоритмы.
Многие умные люди написали очень эффективный код для обычных операций линейной алгебры , используя описанные выше приемы и многое другое, и обычно даже с глупыми приемами, специфичными для платформы. Следовательно, преобразование вашей формулы в умножение матриц и последующее выполнение этих вычислений путем вызова библиотеки зрелой линейной алгебры выигрывают от этих усилий по оптимизации. Напротив, если вы просто напишите формулу очевидным образом на языке высокого уровня, машинный код, который в конечном итоге будет сгенерирован, не будет использовать все эти приемы и не будет таким быстрым. Это также верно, если вы берете матричную формулировку и реализуете ее, вызывая подпрограмму умножения наивной матрицы, которую вы написали сами (опять же, очевидным образом).
Быстрое создание кода требует работы , а зачастую и большой работы, если вы хотите получить последнюю унцию производительности. Поскольку многие важные вычисления могут быть выражены как комбинация пары операций линейной алгебры, экономически выгодно создавать высоко оптимизированный код для этих операций. Ваш единый специализированный вариант использования? Никто не заботится об этом, кроме вас, поэтому оптимизировать его не экономно.