Isso pode parecer óbvio, mas os computadores não executam fórmulas , eles executam código e quanto tempo essa execução leva depende diretamente do código que eles executam e apenas indiretamente de qualquer conceito implementado por esse código. Dois trechos de código logicamente idênticos podem ter características de desempenho muito diferentes. Alguns motivos que provavelmente surgem especificamente na multiplicação de matrizes:
- Usando vários threads. Quase não existe CPU moderna que não tenha múltiplos núcleos, muitos têm até 8, e máquinas especializadas para computação de alto desempenho podem facilmente ter 64 em vários soquetes. Escrever código da maneira óbvia, em uma linguagem de programação normal, usa apenas um deles. Em outras palavras, ele pode usar menos de 2% dos recursos de computação disponíveis da máquina em que está sendo executada.
- Usando instruções SIMD (confundidamente, isso também é chamado de "vetorização", mas em um sentido diferente do que está nas citações de texto da pergunta). Em essência, em vez de 4 ou 8 instruções aritméticas escalares, forneça à CPU uma instrução que execute aritmética em 4 ou 8 registros ou mais em paralelo. Isso pode literalmente fazer alguns cálculos (quando eles são perfeitamente independentes e adequados ao conjunto de instruções) 4 ou 8 vezes mais rápido.
- Fazendo uso mais inteligente do cache . O acesso à memória é mais rápido se forem temporal e espacialmente coerentes , ou seja, acessos consecutivos são para endereços próximos e, ao acessar um endereço duas vezes, você o acessa duas vezes em sucessão rápida, em vez de com uma longa pausa.
- Usando aceleradores como GPUs. Esses dispositivos são bestas muito diferentes das CPUs e programá-los com eficiência é uma forma de arte totalmente própria. Por exemplo, eles têm centenas de núcleos, agrupados em grupos de algumas dezenas de núcleos, e esses grupos compartilham recursos - eles compartilham alguns KiB de memória que são muito mais rápidos que a memória normal e quando qualquer núcleo do grupo executa um
if
Todos os outros membros desse grupo precisam esperar por ela.
- Distribua o trabalho em várias máquinas (muito importante em supercomputadores!), Que introduz um enorme conjunto de novas dores de cabeça, mas pode, é claro, dar acesso a recursos computacionais muito maiores.
- Algoritmos mais inteligentes. Para a multiplicação de matrizes, o algoritmo O (n ^ 3) simples, otimizado adequadamente com os truques acima, geralmente é mais rápido que os subcúbicos para tamanhos razoáveis de matriz, mas às vezes eles vencem. Para casos especiais, como matrizes esparsas, é possível escrever algoritmos especializados.
Muitas pessoas inteligentes escreveram códigos muito eficientes para operações comuns de álgebra linear , usando os truques acima e muito mais e geralmente até com truques estúpidos específicos da plataforma. Portanto, transformar sua fórmula em uma multiplicação de matrizes e implementar esse cálculo chamando para uma biblioteca de álgebra linear madura se beneficia desse esforço de otimização. Por outro lado, se você simplesmente escrever a fórmula da maneira óbvia em uma linguagem de alto nível, o código da máquina que é gerado eventualmente não usará todos esses truques e não será tão rápido. Isso também é verdade se você pegar a formulação da matriz e implementá-la chamando uma rotina de multiplicação de matriz ingênua que você mesmo escreveu (novamente, da maneira óbvia).
Tornar o código rápido exige trabalho e, muitas vezes, bastante trabalho se você deseja a última gota de desempenho. Como muitos cálculos importantes podem ser expressos como combinação de algumas operações de álgebra linear, é econômico criar código altamente otimizado para essas operações. Seu caso de uso especializado único? Ninguém se importa com isso, exceto você, portanto, otimizar o diabo não é econômico.