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.