Este tipo de pregunta es recurrente y debe responderse con más claridad que "MATLAB usa bibliotecas altamente optimizadas" o "MATLAB usa MKL" por una vez en Stack Overflow.
Historia:
La multiplicación de matrices (junto con la matriz de vectores, la multiplicación de vectores y muchas de las descomposiciones de matrices) es (son) los problemas más importantes en álgebra lineal. Los ingenieros han estado resolviendo estos problemas con las computadoras desde los primeros días.
No soy un experto en la historia, pero aparentemente en aquel entonces, todos reescribieron su versión FORTRAN con bucles simples. Luego llegó una cierta estandarización, con la identificación de "núcleos" (rutinas básicas) que la mayoría de los problemas de álgebra lineal necesitaban para ser resueltos. Estas operaciones básicas se estandarizaron luego en una especificación llamada: Subprogramas de álgebra lineal básica (BLAS). Los ingenieros podrían llamar a estas rutinas BLAS estándar y bien probadas en su código, haciendo su trabajo mucho más fácil.
BLAS:
BLAS evolucionó desde el nivel 1 (la primera versión que definió las operaciones escalar-vector y vector-vector) al nivel 2 (operaciones de matriz de vectores) al nivel 3 (operaciones de matriz-matriz), y proporcionó más y más "núcleos" tan estandarizados. y más de las operaciones fundamentales de álgebra lineal. Las implementaciones originales de FORTRAN 77 todavía están disponibles en el sitio web de Netlib .
Hacia un mejor rendimiento:
Entonces, a lo largo de los años (especialmente entre las versiones de nivel 1 y nivel 2 de BLAS: principios de los 80), el hardware cambió, con el advenimiento de las operaciones vectoriales y las jerarquías de caché. Estas evoluciones permitieron aumentar sustancialmente el rendimiento de las subrutinas BLAS. Luego, diferentes proveedores llegaron junto con su implementación de rutinas BLAS que eran cada vez más eficientes.
No conozco todas las implementaciones históricas (no nací o era un niño en ese entonces), pero dos de las más notables salieron a principios de la década de 2000: Intel MKL y GotoBLAS. Su Matlab utiliza el Intel MKL, que es un BLAS muy bueno y optimizado, y eso explica el gran rendimiento que ve.
Detalles técnicos sobre la multiplicación de matrices:
Entonces, ¿por qué Matlab (el MKL) es tan rápido en dgemm
(multiplicación matriz-matriz general de doble precisión)? En términos simples: porque utiliza la vectorización y el buen almacenamiento en caché de datos. En términos más complejos: vea el artículo proporcionado por Jonathan Moore.
Básicamente, cuando realiza su multiplicación en el código C ++ que proporcionó, no es compatible con la caché. Dado que sospecho que creó una matriz de punteros para alinear las matrices, sus accesos en su bucle interno a la columna k-ésima de "matice2": matice2[m][k]
son muy lentos. De hecho, cuando accede matice2[0][k]
, debe obtener el elemento k-ésimo de la matriz 0 de su matriz. Luego, en la siguiente iteración, debe acceder matice2[1][k]
, que es el elemento k-ésimo de otra matriz (la matriz 1). Luego, en la siguiente iteración, accede a otra matriz, y así sucesivamente ... Dado que toda la matriz matice2
no puede caber en las cachés más altas (es de 8*1024*1024
bytes grandes), el programa debe recuperar el elemento deseado de la memoria principal, perdiendo una gran cantidad de hora.
Si acaba de transponer la matriz, de modo que los accesos estén en direcciones de memoria contiguas, su código ya se ejecutará mucho más rápido porque ahora el compilador puede cargar filas completas en la memoria caché al mismo tiempo. Solo prueba esta versión 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();
Entonces puede ver cómo solo la localidad de caché aumentó el rendimiento de su código de manera bastante sustancial. Ahora, las dgemm
implementaciones reales explotan eso a un nivel muy extenso: realizan la multiplicación en bloques de la matriz definida por el tamaño del TLB (búfer de traducción al lado, larga historia: lo que efectivamente se puede almacenar en caché), para que se transmitan al procesador exactamente la cantidad de datos que puede procesar. El otro aspecto es la vectorización, utilizan las instrucciones vectorizadas del procesador para un rendimiento óptimo de la instrucción, lo que realmente no puede hacer desde su código C ++ multiplataforma.
Finalmente, las personas que afirman que se debe al algoritmo de Strassen o Coppersmith-Winograd están equivocadas, ambos algoritmos no son implementables en la práctica, debido a las consideraciones de hardware mencionadas anteriormente.