Ce genre de question est récurrent et devrait recevoir une réponse plus claire que "MATLAB utilise des bibliothèques hautement optimisées" ou "MATLAB utilise le MKL" pour une fois sur Stack Overflow.
L'histoire:
La multiplication matricielle (avec matrice-vecteur, multiplication vecteur-vecteur et plusieurs des décompositions matricielles) est (sont) les problèmes les plus importants en algèbre linéaire. Les ingénieurs résolvent ces problèmes avec les ordinateurs depuis les premiers jours.
Je ne suis pas un expert en histoire, mais apparemment à l'époque, tout le monde vient de réécrire sa version FORTRAN avec de simples boucles. Une certaine normalisation est alors venue avec l'identification des «noyaux» (routines de base) dont la plupart des problèmes d'algèbre linéaire avaient besoin pour être résolus. Ces opérations de base ont ensuite été standardisées dans une spécification appelée: Basic Linear Algebra Subprograms (BLAS). Les ingénieurs pourraient alors appeler ces routines BLAS standard et bien testées dans leur code, facilitant ainsi leur travail.
BLAS:
BLAS a évolué du niveau 1 (la première version définissant les opérations vectorielles scalaires et vectorielles) au niveau 2 (opérations vectorielles-matrices) au niveau 3 (opérations matrices-matrices), et a fourni de plus en plus de «noyaux» donc standardisés plus et plus des opérations fondamentales d'algèbre linéaire. Les implémentations originales de FORTRAN 77 sont toujours disponibles sur le site Web de Netlib .
Vers de meilleures performances:
Ainsi, au fil des années (notamment entre les versions BLAS niveau 1 et niveau 2: début des années 80), le matériel a changé, avec l'avènement des opérations vectorielles et des hiérarchies de cache. Ces évolutions ont permis d'augmenter considérablement les performances des sous-programmes BLAS. Différents éditeurs sont alors venus avec leur implémentation de routines BLAS qui étaient de plus en plus efficaces.
Je ne connais pas toutes les implémentations historiques (je ne suis pas né ou enfant à l'époque), mais deux des plus notables sont sorties au début des années 2000: l'Intel MKL et le GotoBLAS. Votre Matlab utilise l'Intel MKL, qui est un très bon BLAS optimisé, et cela explique les excellentes performances que vous voyez.
Détails techniques sur la multiplication matricielle:
Alors pourquoi Matlab (le MKL) est-il si rapide à dgemm
(multiplication matrice-matrice générale à double précision)? En termes simples: parce qu'il utilise la vectorisation et une bonne mise en cache des données. En termes plus complexes: voir l' article fourni par Jonathan Moore.
Fondamentalement, lorsque vous effectuez votre multiplication dans le code C ++ que vous avez fourni, vous n'êtes pas du tout compatible avec le cache. Puisque je soupçonne que vous avez créé un tableau de pointeurs vers des tableaux de lignes, vos accès dans votre boucle interne à la k-ème colonne de "matice2": matice2[m][k]
sont très lents. En effet, lorsque vous y accédez matice2[0][k]
, vous devez récupérer le k-ième élément du tableau 0 de votre matrice. Ensuite, dans l'itération suivante, vous devez accéder matice2[1][k]
, qui est le k-ième élément d'un autre tableau (le tableau 1). Ensuite, dans l'itération suivante, vous accédez à un autre tableau, et ainsi de suite ... Comme la matrice entière matice2
ne peut pas tenir dans les caches les plus élevés (ses 8*1024*1024
octets sont grands), le programme doit récupérer l'élément souhaité de la mémoire principale, perdant beaucoup de temps.
Si vous venez de transposer la matrice, afin que les accès soient dans des adresses mémoire contiguës, votre code s'exécuterait déjà beaucoup plus rapidement car maintenant le compilateur peut charger des lignes entières dans le cache en même temps. Essayez simplement cette version modifiée:
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();
Vous pouvez donc voir à quel point la seule localité du cache a considérablement augmenté les performances de votre code. Désormais, les dgemm
implémentations réelles exploitent cela à un niveau très étendu: elles effectuent la multiplication sur des blocs de la matrice définie par la taille du TLB (Translation lookaside buffer, long story short: what can effective cached), de sorte qu'ils streament vers le processeur exactement la quantité de données qu'il peut traiter. L'autre aspect est la vectorisation, ils utilisent les instructions vectorisées du processeur pour un débit d'instructions optimal, ce que vous ne pouvez pas vraiment faire à partir de votre code C ++ multiplateforme.
Enfin, les personnes prétendant que c'est à cause de l'algorithme de Strassen ou Coppersmith – Winograd ont tort, ces deux algorithmes ne sont pas implémentables en pratique, à cause des considérations matérielles mentionnées ci-dessus.