Diese Art von Frage kommt immer wieder vor und sollte klarer beantwortet werden als "MATLAB verwendet hochoptimierte Bibliotheken" oder "MATLAB verwendet die MKL" einmal bei Stapelüberlauf.
Geschichte:
Die Matrixmultiplikation (zusammen mit der Matrixvektor-, Vektorvektormultiplikation und vielen der Matrixzerlegungen) ist (sind) das wichtigste Problem in der linearen Algebra. Ingenieure haben diese Probleme seit den Anfängen mit Computern gelöst.
Ich bin kein Experte für die Geschichte, aber anscheinend hat damals jeder seine FORTRAN-Version mit einfachen Schleifen umgeschrieben. Dann kam eine gewisse Standardisierung mit der Identifizierung von "Kerneln" (Grundroutinen), die die meisten linearen Algebra-Probleme brauchten, um gelöst zu werden. Diese Grundoperationen wurden dann in einer Spezifikation standardisiert, die als Grundlegende lineare Algebra-Unterprogramme (BLAS) bezeichnet wurde. Ingenieure könnten diese standardmäßigen, gut getesteten BLAS-Routinen dann in ihrem Code aufrufen, was ihre Arbeit erheblich erleichtert.
BLAS:
BLAS entwickelte sich von Level 1 (der ersten Version, die Skalarvektor- und Vektorvektoroperationen definierte) zu Level 2 (Vektormatrixoperationen) zu Level 3 (Matrixmatrixoperationen) und lieferte immer mehr "Kernel", die so standardisiert waren und mehr der grundlegenden linearen Algebraoperationen. Die ursprünglichen FORTRAN 77-Implementierungen sind weiterhin auf der Netlib-Website verfügbar .
Auf dem Weg zu einer besseren Leistung:
Im Laufe der Jahre (insbesondere zwischen den Versionen BLAS Level 1 und Level 2: Anfang der 80er Jahre) änderte sich die Hardware mit dem Aufkommen von Vektoroperationen und Cache-Hierarchien. Diese Entwicklungen ermöglichten es, die Leistung der BLAS-Subroutinen erheblich zu steigern. Verschiedene Anbieter implementierten dann BLAS-Routinen, die immer effizienter wurden.
Ich kenne nicht alle historischen Implementierungen (ich war damals noch nicht geboren oder ein Kind), aber zwei der bemerkenswertesten kamen Anfang der 2000er Jahre heraus: Intel MKL und GotoBLAS. Ihr Matlab verwendet Intel MKL, ein sehr gutes, optimiertes BLAS, und das erklärt die großartige Leistung, die Sie sehen.
Technische Details zur Matrixmultiplikation:
Warum ist Matlab (die MKL) so schnell dgemm
(allgemeine Matrix-Matrix-Multiplikation mit doppelter Genauigkeit)? In einfachen Worten: weil es Vektorisierung und gutes Zwischenspeichern von Daten verwendet. In komplexeren Begriffen: siehe den Artikel von Jonathan Moore.
Wenn Sie Ihre Multiplikation in dem von Ihnen bereitgestellten C ++ - Code durchführen, sind Sie grundsätzlich überhaupt nicht cachefreundlich. Da ich vermute, dass Sie ein Array von Zeigern auf Zeilenarrays erstellt haben, sind Ihre Zugriffe in Ihrer inneren Schleife auf die k-te Spalte von "matice2": matice2[m][k]
sehr langsam. In der Tat müssen Sie beim Zugriff matice2[0][k]
das k-te Element des Arrays 0 Ihrer Matrix erhalten. In der nächsten Iteration müssen Sie dann auf matice2[1][k]
das k-te Element eines anderen Arrays (das Array 1) zugreifen . In der nächsten Iteration greifen Sie dann auf ein weiteres Array zu und so weiter ... Da die gesamte Matrix matice2
nicht in die höchsten Caches passt (sie ist 8*1024*1024
Bytes groß), muss das Programm das gewünschte Element aus dem Hauptspeicher abrufen und dabei viel verlieren Zeit.
Wenn Sie nur die Matrix transponiert hätten, sodass die Zugriffe auf zusammenhängende Speicheradressen erfolgen würden, würde Ihr Code bereits viel schneller ausgeführt, da der Compiler jetzt ganze Zeilen gleichzeitig in den Cache laden kann. Probieren Sie einfach diese modifizierte Version aus:
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();
So können Sie sehen, wie nur die Cache-Lokalität die Leistung Ihres Codes erheblich gesteigert hat. Jetzt echtdgemm
Implementierungen dies auf einer sehr umfangreichen Ebene: Sie führen die Multiplikation mit Blöcken der Matrix durch, die durch die Größe des TLB definiert sind (Translation Lookaside Buffer, lange Rede, kurzer Sinn: Was kann effektiv zwischengespeichert werden), so dass sie zum Prozessor streamen genau die Datenmenge, die es verarbeiten kann. Der andere Aspekt ist die Vektorisierung. Sie verwenden die vektorisierten Anweisungen des Prozessors für einen optimalen Befehlsdurchsatz, was Sie mit Ihrem plattformübergreifenden C ++ - Code nicht wirklich tun können.
Schließlich sind Leute, die behaupten, es liege am Strassen- oder Coppersmith-Winograd-Algorithmus, falsch. Beide Algorithmen sind aufgrund der oben genannten Hardware-Überlegungen in der Praxis nicht implementierbar.