Esto puede sonar obvio, pero las computadoras no ejecutan fórmulas , ejecutan código , y cuánto tiempo lleva esa ejecución depende directamente del código que ejecutan y solo indirectamente del concepto que implemente el código. Dos partes de código lógicamente idénticas pueden tener características de rendimiento muy diferentes. Algunas razones que probablemente surjan específicamente en la multiplicación de matrices:
- Usando múltiples hilos. Casi no hay una CPU moderna que no tenga múltiples núcleos, muchas tienen hasta 8, y las máquinas especializadas para computación de alto rendimiento pueden tener fácilmente 64 en varios zócalos. Escribir código de la manera obvia, en un lenguaje de programación normal, usa solo uno de esos. En otras palabras, puede usar menos del 2% de los recursos informáticos disponibles de la máquina en la que se está ejecutando.
- Usando instrucciones SIMD (confusamente, esto también se llama "vectorización" pero en un sentido diferente al de las citas de texto en la pregunta). En esencia, en lugar de 4 u 8 más o menos instrucciones aritméticas escalares, dele a la CPU una instrucción que realice operaciones aritméticas en 4 u 8 o más registros en paralelo. Literalmente, esto puede hacer algunos cálculos (cuando son perfectamente independientes y aptos para el conjunto de instrucciones) 4 u 8 veces más rápido.
- Hacer un uso más inteligente del caché . El acceso a la memoria es más rápido si son coherentes temporal y espacialmente , es decir, los accesos consecutivos son a direcciones cercanas y cuando accede a una dirección dos veces, accede a ella dos veces en rápida sucesión en lugar de con una pausa larga.
- Usar aceleradores como las GPU. Estos dispositivos son bestias muy diferentes de las CPU y programarlos de manera eficiente es una forma de arte propia. Por ejemplo, tienen cientos de núcleos, que se agrupan en grupos de unas pocas docenas de núcleos, y estos grupos comparten recursos: comparten algunos KiB de memoria que es mucho más rápido que la memoria normal, y cuando cualquier núcleo del grupo ejecuta un
if
declaración todos los demás en ese grupo tienen que esperarlo.
- Distribuya el trabajo en varias máquinas (¡muy importante en las supercomputadoras!) Que introduce una gran cantidad de nuevos dolores de cabeza pero, por supuesto, puede dar acceso a recursos informáticos mucho mayores.
- Algoritmos más inteligentes. Para la multiplicación de matrices, el algoritmo simple O (n ^ 3), correctamente optimizado con los trucos anteriores, suele ser más rápido que los subcúbicos para tamaños de matriz razonables, pero a veces ganan. Para casos especiales como matrices dispersas, puede escribir algoritmos especializados.
Muchas personas inteligentes han escrito código muy eficiente para operaciones comunes de álgebra lineal , utilizando los trucos anteriores y muchos más, y generalmente incluso con trucos estúpidos específicos de la plataforma. Por lo tanto, transformar su fórmula en una multiplicación de matriz y luego implementar ese cálculo llamando a una biblioteca de álgebra lineal madura se beneficia de ese esfuerzo de optimización. Por el contrario, si simplemente escribe la fórmula de la manera obvia en un lenguaje de alto nivel, el código de máquina que finalmente se genera no utilizará todos esos trucos y no será tan rápido. Esto también es cierto si toma la formulación de la matriz y la implementa llamando a una rutina de multiplicación de matriz ingenua que usted mismo escribió (nuevamente, de manera obvia).
Hacer que el código sea rápido requiere trabajo y, a menudo, mucho trabajo si quieres esa última onza de rendimiento. Debido a que muchos cálculos importantes pueden expresarse como una combinación de un par de operaciones de álgebra lineal, es económico crear un código altamente optimizado para estas operaciones. Sin embargo, ¿su caso de uso especializado único? A nadie le importa eso, excepto a ti, por lo que optimizarlo no es económico.