Diese Frage ist eine Erweiterung von zwei Diskussionen, die kürzlich in den Antworten zu " C ++ vs Fortran for HPC " aufgetaucht sind . Und es ist eher eine Herausforderung als eine Frage ...
Eines der am häufigsten gehörten Argumente für Fortran ist, dass die Compiler einfach besser sind. Da die meisten C / Fortran-Compiler dasselbe Back-End verwenden, sollte der für semantisch äquivalente Programme in beiden Sprachen generierte Code identisch sein. Man könnte jedoch argumentieren, dass C / Fortran für den Compiler mehr oder weniger einfacher zu optimieren ist.
Also habe ich mich für einen einfachen Test entschieden: Ich habe eine Kopie von daxpy.f und daxpy.c bekommen und sie mit gfortran / gcc kompiliert.
Jetzt ist daxpy.c nur eine f2c-Übersetzung von daxpy.f (automatisch generierter Code, hässlich wie zum Teufel), also habe ich diesen Code genommen und ein bisschen aufgeräumt (meet daxpy_c), was im Grunde bedeutete, die innerste Schleife neu zu schreiben als
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
Schließlich habe ich es neu geschrieben (geben Sie daxpy_cvec ein) und dabei die Vektorsyntax von gcc verwendet:
#define vector(elcount, type) __attribute__((vector_size((elcount)*sizeof(type)))) type
vector(2,double) va = { da , da }, *vx, *vy;
vx = (void *)dx; vy = (void *)dy;
for ( i = 0 ; i < (n/2 & ~1) ; i += 2 ) {
vy[i] += va * vx[i];
vy[i+1] += va * vx[i+1];
}
for ( i = n & ~3 ; i < n ; i++ )
dy[i] += da * dx[i];
Beachten Sie, dass ich Vektoren der Länge 2 verwende (das ist alles, was SSE2 zulässt) und zwei Vektoren gleichzeitig verarbeite. Dies liegt daran, dass wir auf vielen Architekturen möglicherweise mehr Multiplikationseinheiten als Vektorelemente haben.
Alle Codes wurden mit gfortran / gcc Version 4.5 mit den Flags "-O3 -Wall -msse2 -march = native -ffast-math -fomit-frame-pointer -malign-double -fstrict-aliasing" kompiliert. Auf meinem Laptop (Intel Core i5 CPU, M560, 2,67 GHz) habe ich folgende Ausgabe erhalten:
pedro@laika:~/work/fvsc$ ./test 1000000 10000
timing 1000000 runs with a vector of length 10000.
daxpy_f took 8156.7 ms.
daxpy_f2c took 10568.1 ms.
daxpy_c took 7912.8 ms.
daxpy_cvec took 5670.8 ms.
Der ursprüngliche Fortran-Code benötigt also etwas mehr als 8,1 Sekunden, die automatische Übersetzung 10,5 Sekunden, die naive C-Implementierung 7,9 und der explizit vektorisierte Code 5,6, etwas weniger.
Das heißt, Fortran ist etwas langsamer als die naive C-Implementierung und 50% langsamer als die vektorisierte C-Implementierung.
Hier ist die Frage: Ich bin ein gebürtiger C-Programmierer und daher ziemlich zuversichtlich, dass ich an diesem Code gute Arbeit geleistet habe, aber der Fortran-Code wurde zuletzt 1993 überarbeitet und ist möglicherweise etwas veraltet. Kann irgendjemand einen besseren Job machen, dh wettbewerbsfähiger als eine der beiden C-Versionen, da ich mich in Fortran nicht so wohl fühle wie andere hier?
Kann jemand diesen Test auch mit icc / ifort ausprobieren? Die Vektorsyntax wird wahrscheinlich nicht funktionieren, aber ich wäre gespannt, wie sich die naive C-Version dort verhält. Gleiches gilt für alle mit xlc / xlf herumliegen.
Ich habe die Quellen und ein Makefile hier hochgeladen . Um genaue Timings zu erhalten, setzen Sie CPU_TPS in test.c auf die Anzahl der Hz in Ihrer CPU. Wenn Sie Verbesserungen an einer der Versionen finden, posten Sie diese bitte hier!
Aktualisieren:
Ich habe den Online-Dateien den Testcode von stali hinzugefügt und ihn mit einer C-Version ergänzt. Ich habe die Programme dahingehend modifiziert, dass 1'000'000 Schleifen auf Vektoren der Länge 10'000 ausgeführt werden, um mit dem vorherigen Test übereinzustimmen (und weil meine Maschine keine Vektoren der Länge 1'000'000'000 zuweisen konnte, wie im Original von stali Code). Da die Zahlen jetzt etwas kleiner sind, habe ich die Option verwendet -par-threshold:50
, um die Wahrscheinlichkeit der Parallelisierung des Compilers zu erhöhen. Die verwendete icc / ifort Version ist 12.1.2 20111128 und die Ergebnisse sind wie folgt
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_c
3.27user 0.00system 0:03.27elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_f
3.29user 0.00system 0:03.29elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_c
4.89user 0.00system 0:02.60elapsed 188%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_f
4.91user 0.00system 0:02.60elapsed 188%CPU
Zusammenfassend sind die Ergebnisse für alle praktischen Zwecke sowohl für die C- als auch für die Fortran-Version identisch, und beide Codes werden automatisch parallelisiert. Beachten Sie, dass die schnellen Zeiten im Vergleich zum vorherigen Test auf die Verwendung von Gleitkomma-Arithmetik mit einfacher Genauigkeit zurückzuführen sind!
Aktualisieren:
Obwohl mir die Beweislast hier nicht wirklich gefällt, habe ich das Matrixmultiplikationsbeispiel von stali in C neu codiert und den Dateien im Web hinzugefügt . Hier sind die Ergebnisse der Tripple-Schleife für eine und zwei CPUs:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
triple do time 3.46421700000000
3.63user 0.06system 0:03.70elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_c 2500
triple do time 3.431997791385768
3.58user 0.10system 0:03.69elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
triple do time 5.09631900000000
5.26user 0.06system 0:02.81elapsed 189%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_c 2500
triple do time 2.298916975280899
4.78user 0.08system 0:02.62elapsed 184%CPU
Beachten Sie, dass cpu_time
in Fortran die CPU-Zeit und nicht die Wanduhr-Zeit gemessen wird. Deshalb habe ich die Aufrufe eingepackt time
, um sie für 2 CPUs zu vergleichen. Es gibt keinen wirklichen Unterschied zwischen den Ergebnissen, außer dass die C-Version auf zwei Kernen etwas besser abschneidet.
Nun zum matmul
Befehl, natürlich nur in Fortran, da diese Eigenschaft in C nicht verfügbar ist:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
matmul time 23.6494780000000
23.80user 0.08system 0:23.91elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
matmul time 26.6176640000000
26.75user 0.10system 0:13.62elapsed 197%CPU
Beeindruckend. Das ist absolut schrecklich. Kann jemand herausfinden, was ich falsch mache, oder erklären, warum dies irgendwie immer noch eine gute Sache ist?
Ich habe die dgemm
Aufrufe nicht zum Benchmark hinzugefügt , da es sich um Bibliotheksaufrufe für dieselbe Funktion in der Intel MKL handelt.
Kann jemand für zukünftige Tests ein Beispiel vorschlagen, von dem bekannt ist , dass es in C langsamer ist als in Fortran?
Aktualisieren
Um die Behauptung von stali zu bestätigen, dass das matmul
Intrinsic "eine Größenordnung" schneller ist als das explizite Matrixprodukt auf kleineren Matrizen, habe ich seinen eigenen Code modifiziert, um Matrizen der Größe 100x100 mit beiden Methoden zu multiplizieren, jeweils 10'000-mal. Die Ergebnisse für eine und zwei CPUs lauten wie folgt:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 10000 100
matmul time 3.61222500000000
triple do time 3.54022200000000
7.15user 0.00system 0:07.16elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 10000 100
matmul time 4.54428400000000
triple do time 4.31626900000000
8.86user 0.00system 0:04.60elapsed 192%CPU
Aktualisieren
Grisu weist zu Recht darauf hin, dass gcc ohne Optimierungen Operationen auf komplexen Zahlen in Bibliotheksfunktionsaufrufe umwandelt, während gfortran sie in ein paar Anweisungen einfügt.
Der C-Compiler generiert den gleichen, kompakten Code, wenn die Option -fcx-limited-range
gesetzt ist, dh der Compiler wird angewiesen, mögliche Über- / Unterläufe in den Zwischenwerten zu ignorieren. Diese Option ist in gfortran standardmäßig aktiviert und kann zu falschen Ergebnissen führen. Das Erzwingen -fno-cx-limited-range
von Gfortran änderte nichts.
Dies ist also eigentlich ein Argument gegen die Verwendung von Gfortran für numerische Berechnungen: Operationen mit komplexen Werten können über- / unterlaufen, selbst wenn die korrekten Ergebnisse im Gleitkommabereich liegen. Dies ist eigentlich ein Fortran-Standard. In gcc oder in C99 im Allgemeinen ist die Standardeinstellung, dass die Dinge streng ausgeführt werden (lesen Sie IEEE-754-konform), sofern nicht anders angegeben.
Erinnerung: Bitte beachten Sie, dass die Hauptfrage lautete, ob Fortran-Compiler besseren Code als C-Compiler produzieren. Dies ist nicht der Ort für Diskussionen über die allgemeinen Vorzüge einer Sprache gegenüber einer anderen. Was mich wirklich interessiert, ist, ob irgendjemand einen Weg finden kann, Gfortran zu überreden, ein so effizientes Daxpy wie das in C unter Verwendung expliziter Vektorisierung zu erzeugen, da dies die Probleme veranschaulicht, sich ausschließlich für die SIMD-Optimierung auf den Compiler verlassen zu müssen, oder a Fall, in dem ein Fortran-Compiler sein C-Gegenstück übertrifft.
quelle
restrict
Schlüsselwort an, das dem Compiler genau sagt, dass: angenommen wird, dass sich ein Array nicht mit einer anderen Datenstruktur überschneidet.Antworten:
Der Unterschied in Ihrem Timing scheint auf das manuelle Abrollen des Fortran daxpy zurückzuführen zu sein . Die folgenden Timings beziehen sich auf einen 2,67-GHz-Xeon X5650, der den Befehl verwendet
Intel 11.1-Compiler
Fortran mit manuellem Abrollen: 8,7 Sek.
Fortran ohne manuelles Abrollen: 5,8 Sek.
C ohne manuelles Abrollen: 5,8 Sek
GNU 4.1.2-Compiler
Fortran mit manuellem Abrollen: 8,3 Sek.
Fortran ohne manuelles Abrollen: 13,5 Sek.
C ohne manuelles Abrollen: 13,6 Sek.
C mit Vektorattributen: 5,8 Sek
GNU 4.4.5-Compiler
Fortran mit manuellem Abrollen: 8,1 Sek.
Fortran ohne manuelles Abrollen: 7,4 Sek.
C ohne manuelles Abrollen: 8,5 Sek.
C mit Vektorattributen: 5,8 Sek
Schlussfolgerungen
Zeit, kompliziertere Routinen wie dgemv und dgemm zu testen?
quelle
Ich komme zu spät zu dieser Party, daher fällt es mir schwer, das Hin und Her von oben zu verfolgen. Die Frage ist groß und ich denke, wenn Sie interessiert sind, könnte sie in kleinere Teile zerlegt werden. Eine Sache, die mich interessierte, war einfach die Leistung Ihrer
daxpy
Varianten und ob Fortran in diesem sehr einfachen Code langsamer als C ist.Unter meinem Laptop (Macbook Pro, Intel Core i7, 2,66 GHz) hängt die relative Leistung Ihrer handvektorisierten C-Version und der nicht handvektorisierten Fortran-Version vom verwendeten Compiler ab (mit Ihren eigenen Optionen):
Es scheint also, dass GCC die Schleife in der 4.6-Verzweigung besser vektorisieren konnte als zuvor.
In der Gesamtdebatte kann man, wie in der Assemblersprache, ziemlich schnell und optimierten Code sowohl in C als auch in Fortran schreiben. Ich möchte jedoch auf eine Sache hinweisen: So wie Assembler mühsamer zu schreiben ist als C, aber Ihnen eine genauere Kontrolle darüber gibt, was von der CPU ausgeführt wird, ist C niedriger als Fortran. Auf diese Weise haben Sie mehr Kontrolle über die Details. Dies kann bei der Optimierung hilfreich sein, wenn die Fortran-Standardsyntax (oder ihre Herstellererweiterungen) möglicherweise nicht über ausreichende Funktionen verfügt. Ein Fall ist die explizite Verwendung von Vektortypen, ein anderer ist die Möglichkeit, die Ausrichtung von Variablen von Hand festzulegen, wozu Fortran nicht in der Lage ist.
quelle
Die Art und Weise, wie ich AXPY in Fortran schreiben würde, ist etwas anders. Es ist die genaue Übersetzung der Mathematik.
m_blas.f90
Rufen wir nun die obige Routine in einem Programm auf.
test.f90
Jetzt kompilieren wir es und führen es aus ...
Beachten Sie, dass ich keine Schleifen oder expliziten OpenMP- Anweisungen verwende. Wäre dies in C möglich (dh keine Verwendung von Schleifen und automatische Parallelisierung)? Ich benutze kein C, also weiß ich es nicht.
quelle
icc
auch eine automatische Parallelisierung durch. Ich habe eine Dateiicctest.c
zu den anderen Quellen hinzugefügt . Können Sie es mit denselben Optionen wie oben kompilieren, ausführen und die Zeitangaben protokollieren? Ich musste meinem Code eine printf-Anweisung hinzufügen, um zu vermeiden, dass gcc alles optimiert. Dies ist nur ein kurzer Hack und ich hoffe er ist fehlerfrei!Ich finde es nicht nur interessant, wie ein Compiler den Code für moderne Hardware optimiert. Insbesondere zwischen GNU C und GNU Fortran kann die Codegenerierung sehr unterschiedlich sein.
Betrachten wir also ein anderes Beispiel, um die Unterschiede zwischen ihnen zu zeigen.
Unter Verwendung komplexer Zahlen erzeugt der GNU C-Compiler einen großen Overhead für nahezu grundlegende arithmetische Operationen mit komplexen Zahlen. Der Fortran-Compiler liefert viel besseren Code. Schauen wir uns das folgende kleine Beispiel in Fortran an:
ergibt (gfortran -g -o complex.fo -c complex.f95; objdump -d -S complex.fo):
Welches sind 39 Bytes Maschinencode. Wenn wir das gleiche in C betrachten
und werfen Sie einen Blick auf die Ausgabe (auf die gleiche Weise wie oben), erhalten wir:
Der 39-Byte-Maschinencode, auf den sich jedoch der Funktionsschritt 57 bezieht, erledigt den richtigen Teil der Arbeit und führt die gewünschte Operation aus. Wir haben also 27-Byte-Maschinencode, um die Mehrfachoperation auszuführen. Die dahinter stehende Funktion ist muldc3 von
libgcc_s.so
und hat eine Grundfläche von 1375 Byte im Maschinencode. Dies verlangsamt den Code drastisch und liefert bei Verwendung eines Profilers eine interessante Ausgabe.Wenn wir die obigen BLAS-Beispiele für
zaxpy
denselben Test implementieren und ausführen, sollte der Fortran-Compiler bessere Ergebnisse liefern als der C-Compiler.(Für dieses Experiment habe ich GCC 4.4.3 verwendet, aber ich habe dieses Verhalten bei einem anderen GCC-Release bemerkt.)
Meiner Meinung nach denken wir also nicht nur an Parallelisierung und Vektorisierung, wenn wir uns überlegen, welcher Compiler der bessere ist, sondern auch, wie grundlegende Dinge in Assembler-Code übersetzt werden. Wenn diese Übersetzung fehlerhaften Code ergibt, kann die Optimierung diese Dinge nur als Eingabe verwenden.
quelle
complex.c
und es online zum Code hinzugefügt. Ich musste alle Ein- / Ausgaben hinzufügen, um sicherzustellen, dass nichts optimiert ist. Ich werde nur angerufen,__muldc3
wenn ich nicht benutze-ffast-math
. Mit-O2 -ffast-math
bekomme ich 9 Zeilen inline Assembler. Kannst du das bestätigen?-ffast-math
) sollten Sie Fortran nicht für Ihre komplexwertigen Berechnungen verwenden. Wie ich im Update zu meiner Frage beschreibe,-ffast-math
oder allgemeiner,-fcx-limited-range
zwingt gcc dazu, dieselben Nicht-IEEE-Berechnungen mit eingeschränktem Bereich zu verwenden, wie sie in Fortran Standard sind . Wenn Sie also den gesamten Bereich komplexer Werte und korrekter Infs und NaNs benötigen, sollten Sie Fortran nicht verwenden ...Leute,
Ich fand diese Diskussion sehr interessant, war aber überrascht zu sehen, dass die Neuordnung der Loops im Matmul-Beispiel das Bild veränderte. Ich habe keinen Intel-Compiler auf meinem aktuellen Rechner, daher verwende ich gfortran, sondern schreibe die Schleifen in der mm_test.f90 um
änderte die gesamten Ergebnisse für meine Maschine.
Die Timing-Ergebnisse der vorherigen Version waren:
wohingegen mit den wie oben geordneten Dreifachschleifen:
Dies ist gcc / gfortran 4.7.2 20121109 auf einer Intel (R) Core (TM) i7-2600K-CPU mit 3,40 GHz
Es wurden Compiler-Flags aus dem Makefile verwendet, das ich hier bekommen habe ...
quelle
Es sind keine Sprachen, die den Code schneller laufen lassen, obwohl sie helfen. Es sind der Compiler, die CPU und das Betriebssystem, die die Ausführung von Codes beschleunigen. Der Vergleich von Sprachen ist nur eine Fehlbezeichnung, nutzlos und bedeutungslos. Es macht überhaupt keinen Sinn, weil Sie zwei Variablen vergleichen: die Sprache und den Compiler. Wenn ein Code schneller ausgeführt wird, wissen Sie nicht, wie viel die Sprache oder wie viel der Compiler ist. Ich verstehe nicht, warum die Informatik-Community das einfach nicht versteht :-(
quelle