2016-06-07 9 views
1

Ich wollte beweisen, dass C++ mit Move-Semantik ist viel schneller als Java und ich landete in einer großen Überraschung, dass es tatsächlich viel langsamer ist.C++ move Semantik-Performance im Vergleich zu Java

EDIT: Ich bin am Einfügen von Elementen in einem Vektor, obwohl ich weiß, dass solche Einfügung passt besser mit einer Liste. Dies ist jedoch ein typischer Benchmark für die Benchmark-Bewegungssemantik in C++. Daher wollte ich diesen speziellen Test nutzen, um die Unterschiede zu Java zu erkennen.

ich einen Fehler gemacht haben könnte getan: was habe ich die folgenden zwei Programme: http://melpon.org/wandbox/permlink/abZlAmZrOC9fJV8r und http://melpon.org/wandbox/permlink/3QGQMEIoF5kc3xtY

Das Ergebnis ist ziemlich überraschend, da es zeigt, dass Java schneller mit einem Faktor 6! soll ich den Code könnte auf verschiedenen Maschinen laufen, so habe ich versucht, auf meinem Rechner mit msvc 2012x64 und den folgenden Kompilierungsoptionen:

cl/EHsc/Ox cppPerf.cpp

Im Vergleich zu

jdk1.8.0_73 \ bin \ javac.exe javaPerf.java

Das Ergebnis geben ein Verhältnis von 10 in Bevorzugung für Java!

Wie könnte es sein, dass Java ist so viel schneller als C++, die weiterhin Bewegungssemantik verwendet?

Ein weiterer interessanter Punkt ist, dass Java7 mit Leistung gibt ziemlich weit von Java8 aber immer noch besser, dass C++:

EDIT: ich den Code entfernt, die durch die Links zugänglich ist.

EDIT:

Ich glaube, ich fand das Problem, optimierte Java einige Code entfernt. Diese sind Versionen aktualisiert, die zeigen, dass C++ ist viel schneller: http://melpon.org/wandbox/permlink/GsflztloK7ir2jea http://melpon.org/wandbox/permlink/CLwKoZzbfqstDOfn

EDIT: Dies ist ein weiterer Test, die Maßstäbe für C liefert ++ mit Referenzen wie @dasblinkenlight vorgeschlagen, die zeigen große Unterschiede zwischen C++ und Java (Anmerkung I Notiz nicht genutzt unique_ptr, weil ich gegen C++ 03 überprüfen will, und ich wollte Auftrieb vermeiden, damit keine scoped_ptr entweder): http://melpon.org/wandbox/permlink/bYQjNpjsIZu3vp3f

Ergebnisse sind:

C++14: 
USE_MOVE USE_REFERENCES TIME 
    Y    Y   30 to 55ms 
    N    Y   30 to 60ms 

    Y    N   370 to 210ms 
    N    N   390 to 270ms (Implicit move of elements in vectors) 

C++03: 
USE_REFERENCES TIME 
     Y   45 to 70ms 
     N  1370 to 1650ms (No implicit move of elements in vectors) 

Java: 
       11300 to 13000 ms 
+0

Sie ordnen die Zeichenfolgen mit dem Standardzuordner in C++ zu. Wenn ich mich recht erinnere, führen einige Java-Implementierungen standardmäßig eine Pooling nach Typ durch. Außerdem beugen Sie nur einen Zeiger im Java-Fall, aber Sie rufen den Destruktor jeder einzelnen Zeichenfolge im C++ - Fall auf (wenn "auto tmp" den Gültigkeitsbereich verlässt). Das C++ - Äquivalent zu dem, was Java tut, wäre 'std :: string & tmp = vec [i];' – PeterT

+0

Ich habe es als EDIT geschrieben, aber tatsächlich ist der C++ - Code viel schneller, Java optimiert einige Code ... – JeanPhi

+0

Gut mit dem geändert Code Sie Benchmark-String-Verkettung mehr als Sie Benchmarks "Move Semantik versus Referenzen" oder was auch immer Ihr ursprünglicher Punkt war. – PeterT

Antwort

4

Das Problem ist, dass Sie nicht Äpfel mit Äpfeln vergleichen:

  • Java String s sind unveränderlich, so sie in einen Behälter Mengen Kopieren Sie einen Verweis auf Speichern.
  • Wichtiger ist, dass Ihr Code am Anfang des Vektors eingefügt wird, damit der Vektor O-Operationen() ausführt.

Ich denke, der zweite Punkt macht den größten Teil des Unterschieds aus. Das Einfügen am Ende mit vec.insert(vec.end(), std::move(myString)) oder mit push_back und eine entsprechende Änderung in Java sollte mal viel näher zueinander kommen.

Um auch den ersten Punkt zu fixieren, ordnen Sie Ihre Strings dynamisch zu und erstellen stattdessen einen Vektor von std::unique_ptr<std::string>. Dies würde Zeiger kopieren, anstatt Inhalt zu kopieren, wodurch die Leistungszeiten einander näher gebracht werden.

+0

Eigentlich sollte bei der Bewegungssemantik der ganze Vektor verschoben und nicht kopiert werden. Weiterhin habe ich eine R-Wert-Referenz eingefügt, also sollte es nicht langsamer als eine Referenz langsamer sein oder sehe ich etwas falsch? – JeanPhi

+0

@JeanPhi Ich spreche von der: 'vec.insert (vec.begin(), std :: move (myString))' Linie. Hier wird der Inhalt des gesamten Vektors für 10.000 Einfügungen 9999 mal verschoben. – dasblinkenlight

+0

Das ist genau das, was ich messen möchte: Was passiert in einem Kontext, in dem viele Operationen benötigt werden. Dies ist ein Standard-Benchmark, um die Leistungsunterschiede zwischen C++ mit oder ohne Bewegungssemantik zu überprüfen. – JeanPhi

0

C++ vector s insert Methode verwendet Neuzuweisung wenn Insertion nicht am Ende ist:

Da Vektoren ein Array als die ihnen zugrunde liegenden Speicher verwenden, Elementen in anderen Positionen als den Vektor Ende Einführen des Behälters verursacht um alle Elemente zu verschieben, die nach Position zu ihren neuen Positionen waren. Dies ist im Allgemeinen eine ineffiziente Operation im Vergleich zu eine für den gleichen Vorgang durch andere Arten von Sequenz Container durchgeführt (wie Liste oder Forward_list).

Ersetzen Sie vector mit list und Sie werden sehen, dass die Leistungen fast gleich sind.

Vector als eine abstrakte Art fast das gleiche in Java und C++, aber ihre jeweiligen Implementierungen können ernsthaft unterscheiden ...

+0

Danke @ Jean-Baptiste, eigentlich mache ich diesen Test genau zu diesem Zweck, ich möchte solche Einfügevorgänge durchführen und prüfen, wie C++ und Java reagieren. Dies ist ein typischer Benchmark zum Vergleich von C++ mit und ohne Bewegungssemantik. – JeanPhi

1

ich, dass C++ mit move Semantik beweisen wollte, ist viel schneller als Java und ich landeten in einer großen Überraschung, dass es tatsächlich viel langsamer ist.

Wenn dich jemand fragt, ob C++ schneller ist als Java, dreh dich um und geh weg. Die Industrie benötigt keinen solchen Nachweis.

Das Ergebnis ist ziemlich überraschend, da es vorschlägt, dass Java mit ein Faktor 6 schneller ist!

Nein.

In einer Nussschale, ist es das, was C++ tun:

for 10000 times, Do: 
1. measure the literal size 
2. allocate enough memory to hold the literal (EXPENSIVE!) 
3. Copy the literal memory into the newly allocated memory 
4. check if there is enough memory in the vector to hold current size + 1 string objects, if false - allocate new chunk and move all the existing objects to that chunk of memory (EXPENSIVE!) 
5. move the existing strings inside the vector forward by 1 index (EXPENSIVE!) 
6. move the string object to `vec[0]` 
for 10000 times, Do: 
7. build new string objects from r-value reference returned from `vec[i]` 
8. free the string memory (EXPENSIVE!) 

Sie 10000 String-Objekte aus einem wörtlichen bauen, so dass Sie 10000 Stücke von Speichern allcoate haben, die wörtliche Länge wieder neu berechnen und erneut und wiederholt die Zeichenfolgenobjekte innerhalb des Vektors.

Was Java tut:

1. when the program goes up, the JVM builds a string object containing the literal just once 
for 10000 , Do: 
2. copy the pointer (Cheap.) 
3. check if there is enough memory in the vector to hold current size + 1 string pointers , if not - allocate new chunk of memory and copy the existing pointers to that memory block (Cheap.) 
4. move the existing string pointers inside the vector forward by 1 index (Cheap, probably uses `memmove` behind the scenes) 
5. copy the pointer to `Vector[0]` (Cheap.) 
for 10000 , Do: 
6. Copy the string pointer pointed by `vec[i]` 

Der String-Objekt nur einmal erstellt wird, und dessen Zeiger wird immer wieder kopiert. Das Java-Programm funktioniert nicht wie das C++ - Programm.

Also im Grunde zeigt dieses Programm nur, dass das Kopieren von Zeigern billiger ist als das Kopieren von Objekten. nichts, was wir nicht schon wussten. Wenn Sie wirklich faires Benchmarking durchführen wollen, entweder Benchmark-Zeiger in C++, lassen Sie das Java-Programm entweder die Zeichenfolge immer wieder neu klonen.

+0

Das Problem ist nicht da, aber in dem Hinzufügen von Elementen am Anfang der Vektoren in C++ –

+0

Hallo @ David Haim, überprüfen Sie bitte meine Änderungen. Eigentlich hat Java etwas Code optimiert. – JeanPhi

+0

@JeanPhi versuchen, die String-Längen anstelle der Verkettung von Strings zu addieren, und Sie werden sehen, dass Sie tatsächlich nur String-Verkettungsgeschwindigkeit von Java 'String' gemessen haben. Sie müssen wirklich klarstellen, was Sie zeigen möchten, denn Sie messen nicht, was Sie zu messen glauben. Ich meine, das erste, was Java-Leute Ihnen sagen werden, ist, dass Sie einen StringBuilder verwenden sollten, wenn Sie eine Menge Dinge verketten wollen. – PeterT

Verwandte Themen