Esse tipo de pergunta é recorrente e deve ser respondida com mais clareza do que "MATLAB usa bibliotecas altamente otimizadas" ou "MATLAB usa o MKL" pela primeira vez no Stack Overflow.
História:
A multiplicação de matrizes (juntamente com a matriz de vetores, multiplicação de vetores e muitas das decomposições de matrizes) é (são) os problemas mais importantes na álgebra linear. Os engenheiros têm resolvido esses problemas com os computadores desde os primeiros dias.
Não sou especialista em história, mas aparentemente naquela época todo mundo reescreveu sua versão do FORTRAN com loops simples. Alguma padronização surgiu com a identificação de "kernels" (rotinas básicas) que a maioria dos problemas de álgebra linear precisava para ser resolvida. Essas operações básicas foram padronizadas em uma especificação chamada: Subprogramas Básicos de Álgebra Linear (BLAS). Os engenheiros poderiam então chamar essas rotinas BLAS padrão e bem testadas em seu código, facilitando muito o trabalho.
BLAS:
O BLAS evoluiu do nível 1 (a primeira versão que definiu operações de vetor escalar e vetor de vetor) para o nível 2 (operações de matriz vetorial) para o nível 3 (operações de matriz matricial) e forneceu mais e mais "kernels" para padronizar mais e mais das operações fundamentais de álgebra linear. As implementações originais do FORTRAN 77 ainda estão disponíveis no site da Netlib .
Para um melhor desempenho:
Assim, ao longo dos anos (principalmente entre os lançamentos BLAS nível 1 e nível 2: início dos anos 80), o hardware mudou, com o advento das operações de vetor e hierarquias de cache. Essas evoluções permitiram aumentar substancialmente o desempenho das sub-rotinas BLAS. Diferentes fornecedores surgiram com a implementação de rotinas BLAS, cada vez mais eficientes.
Não conheço todas as implementações históricas (eu não nasci ou era criança na época), mas duas das mais notáveis surgiram no início dos anos 2000: o Intel MKL e o GotoBLAS. Seu Matlab usa o Intel MKL, que é um BLAS muito bom e otimizado, o que explica o ótimo desempenho que você vê.
Detalhes técnicos sobre multiplicação de matrizes:
Então, por que o Matlab (o MKL) é tão rápido dgemm
(multiplicação matriz-matriz geral de dupla precisão)? Em termos simples: porque usa vetorização e bom cache de dados. Em termos mais complexos: veja o artigo fornecido por Jonathan Moore.
Basicamente, quando você executa sua multiplicação no código C ++ que você forneceu, você não é totalmente compatível com o cache. Como suspeito que você tenha criado uma matriz de ponteiros para matrizes de linha, seus acessos no loop interno à k-ésima coluna de "matice2": matice2[m][k]
são muito lentos. De fato, quando você acessa matice2[0][k]
, você deve obter o k-ésimo elemento da matriz 0 da sua matriz. Na próxima iteração, você deve acessar matice2[1][k]
, que é o k-ésimo elemento de outra matriz (a matriz 1). Então, na próxima iteração, você acessa outra matriz, e assim por diante ... Como a matriz inteira matice2
não pode se encaixar nos caches mais altos (seus 8*1024*1024
bytes são grandes), o programa deve buscar o elemento desejado da memória principal, perdendo muito Tempo.
Se você acabou de transpor a matriz, para que os acessos estivessem em endereços de memória contíguos, seu código já seria executado muito mais rápido, porque agora o compilador pode carregar linhas inteiras no cache ao mesmo tempo. Apenas tente esta versão modificada:
timer.start();
float temp = 0;
//transpose matice2
for (int p = 0; p < rozmer; p++)
{
for (int q = 0; q < rozmer; q++)
{
tempmat[p][q] = matice2[q][p];
}
}
for(int j = 0; j < rozmer; j++)
{
for (int k = 0; k < rozmer; k++)
{
temp = 0;
for (int m = 0; m < rozmer; m++)
{
temp = temp + matice1[j][m] * tempmat[k][m];
}
matice3[j][k] = temp;
}
}
timer.stop();
Assim, você pode ver como a localidade do cache aumentou o desempenho do seu código de maneira bastante substancial. Agora realdgemm
implementações exploram isso em um nível muito extenso: elas realizam a multiplicação em blocos da matriz definida pelo tamanho do TLB (tradução ao lado do buffer de tradução, longa história: o que pode ser efetivamente armazenado em cache), para que sejam transmitidos ao processador exatamente a quantidade de dados que pode processar. O outro aspecto é a vetorização, eles usam as instruções vetorizadas do processador para obter o melhor rendimento de instruções, o que você realmente não pode fazer com o código C ++ de plataforma cruzada.
Finalmente, as pessoas que afirmam que é por causa do algoritmo de Strassen ou Coppersmith – Winograd estão erradas, esses dois algoritmos não são implementáveis na prática, devido às considerações de hardware mencionadas acima.