Ich habe den folgenden Java-Code mit mehreren großen Arrays, deren Größe sich nie ändert. Es läuft in 1100 ms auf meinem Computer.
Ich habe den gleichen Code in C ++ implementiert und verwendet std::vector
.
Die Zeit der C ++ - Implementierung, in der genau derselbe Code ausgeführt wird, beträgt auf meinem Computer 8800 ms. Was habe ich falsch gemacht, damit es so langsam läuft?
Grundsätzlich macht der Code Folgendes:
for (int i = 0; i < numberOfCells; ++i) {
h[i] = h[i] + 1;
floodedCells[i] = !floodedCells[i];
floodedCellsTimeInterval[i] = !floodedCellsTimeInterval[i];
qInflow[i] = qInflow[i] + 1;
}
Es durchläuft verschiedene Arrays mit einer Größe von ca. 20000.
Sie finden beide Implementierungen unter den folgenden Links:
- Java: https://ideone.com/R8KqjT
- C ++: https://ideone.com/Lu7RpE
(Auf ideone konnte ich die Schleife aufgrund der zeitlichen Begrenzung nur 400-mal statt 2000-mal ausführen. Aber auch hier gibt es einen dreifachen Unterschied.)
std::vector<bool>
verwendet ein Bit pro Element, um Platz zu sparen, was zu einer starken Bitverschiebung führt. Wenn Sie Geschwindigkeit wollen, sollten Sie sich davon fernhalten. Verwenden Siestd::vector<int>
stattdessen.h[i] += 1;
oder (noch besser)++h[i]
besser lesbar ist alsh[i] = h[i] + 1;
, wäre ich etwas überrascht, einen signifikanten Geschwindigkeitsunterschied zwischen ihnen zu sehen. Ein Compiler kann "herausfinden", dass beide dasselbe tun und in beiden Fällen denselben Code generieren (zumindest in den meisten Fällen).Antworten:
Hier ist die C ++ - Version mit den in einer Struktur gesammelten Daten pro Knoten und einem einzelnen verwendeten Vektor dieser Struktur:
Live-Beispiel
Die Zeit ist jetzt 2x so schnell wie die Java-Version. (846 gegen 1631).
Wahrscheinlich hat die JIT das Brennen des Cache beim Zugriff auf Daten überall bemerkt und Ihren Code in eine logisch ähnliche, aber effizientere Reihenfolge umgewandelt.
Ich habe auch die stdio-Synchronisation deaktiviert, da dies nur benötigt wird, wenn Sie
printf
/scanf
mit C ++std::cout
und mischenstd::cin
. Zwar drucken Sie nur wenige Werte aus, aber das Standardverhalten von C ++ beim Drucken ist zu paranoid und ineffizient.Wenn
nEdges
es sich nicht um einen tatsächlichen konstanten Wert handelt, müssen die 3 "Array" -Werte aus dem entfernt werdenstruct
. Das sollte keinen großen Leistungseinbruch verursachen.Möglicherweise können Sie eine weitere Leistungssteigerung erzielen, indem Sie die Werte sortieren,
struct
indem Sie die Größe verringern und so den Speicherbedarf verringern (und den Zugriff auch sortieren, wenn dies keine Rolle spielt). Aber ich bin mir nicht sicher.Als Faustregel gilt, dass ein einzelner Cache-Fehler 100-mal teurer ist als eine Anweisung. Das Anordnen Ihrer Daten zur Cache-Kohärenz hat viel Wert.
Wenn eine Neuanordnung der Daten in a nicht möglich
struct
ist, können Sie Ihre Iteration so ändern, dass sie sich nacheinander über jedem Container befindet.Beachten Sie außerdem, dass die Java- und C ++ - Versionen einige geringfügige Unterschiede aufwiesen. Die eine, die ich entdeckte, war, dass die Java-Version 3 Variablen in der "für jede Kante" -Schleife hat, während die C ++ - nur 2 hatte. Ich habe meine mit Java übereinstimmen lassen. Ich weiß nicht, ob es noch andere gibt.
quelle
Ja, der Cache in der C ++ - Version muss gehämmert werden. Es scheint, dass die JIT besser dafür gerüstet ist.
Wenn Sie das Äußere
for
in isUpdateNeeded () in kürzere Snippets ändern . Der Unterschied verschwindet.Das folgende Beispiel führt zu einer 4-fachen Beschleunigung.
Dies zeigt in angemessenem Maße, dass Cache-Fehler der Grund für die Verlangsamung sind. Es ist auch wichtig zu beachten, dass die Variablen nicht abhängig sind, sodass leicht eine Thread-Lösung erstellt werden kann.
Ordnung wiederhergestellt
Gemäß dem Kommentar von stefans habe ich versucht, sie in einer Struktur unter Verwendung der Originalgrößen zu gruppieren. Dies entfernt den unmittelbaren Cache-Druck auf ähnliche Weise. Das Ergebnis ist, dass die c ++ (CCFLAG -O3) -Version etwa 15% schneller ist als die Java-Version.
Varning weder kurz noch hübsch.
Mein Ergebnis unterscheidet sich geringfügig von Jerry Coffins für die Originalgrößen. Für mich bleiben die Unterschiede. Es könnte durchaus meine Java-Version 1.7.0_75 sein.
quelle
++
in irgendeiner Form?x = x + 1
scheint schrecklich klobig im Vergleich zu++x
.Wie @Stefan in einem Kommentar zur Antwort von @ CaptainGiraffe vermutet hat, gewinnen Sie einiges, wenn Sie einen Vektor von Strukturen anstelle einer Struktur von Vektoren verwenden. Der korrigierte Code sieht folgendermaßen aus:
Mit dem Compiler aus VC ++ 2015 CTP kompiliert
-EHsc -O2b2 -GL -Qpar
, erhalte ich folgende Ergebnisse:Das Kompilieren mit g ++ führt zu einem etwas langsameren Ergebnis:
Auf derselben Hardware erhalte ich mit dem Compiler / JVM von Java 8u45 folgende Ergebnisse:
Dies ist ungefähr 35% langsamer als die Version von VC ++ und ungefähr 16% langsamer als die Version von g ++.
Wenn wir die Anzahl der Iterationen auf das gewünschte Jahr 2000 erhöhen, sinkt die Differenz auf nur 3%, was darauf hindeutet, dass ein Teil des Vorteils von C ++ in diesem Fall einfach ein schnelleres Laden ist (ein beständiges Problem mit Java), das nicht wirklich in der Ausführung selbst liegt. Dies überrascht mich in diesem Fall nicht - die gemessene Berechnung (im veröffentlichten Code) ist so trivial, dass ich bezweifle, dass die meisten Compiler eine ganze Menge tun können, um sie zu optimieren.
quelle
#pragma omp
und (vielleicht) ein wenig Arbeit hinzufügen , um sicherzustellen, dass jede Schleifeniteration unabhängig ist. Das würde ziemlich wenig Arbeit erfordern, um eine ~ Nx-Beschleunigung zu erhalten, wobei N die Anzahl der verfügbaren Prozessorkerne ist.Ich vermute, es geht um die Zuweisung von Speicher.
Ich denke, dass
Java
beim Programmstart ein großer zusammenhängender Block erfasst wird, währendC++
das Betriebssystem im Laufe der Zeit nach Kleinigkeiten gefragt wird.Um diese Theorie auf den Prüfstand zu stellen, habe ich eine Änderung an der
C++
Version vorgenommen und sie lief plötzlich etwas schneller als dieJava
Version:Laufzeit ohne Vorbelegungsvektor:
Laufzeit mit dem Vorbelegungsvektor:
Laufzeit für
Java
Version:quelle
FloodIsolation
können noch an anderer Stelle zugewiesen werden.