2014-12-22 3 views
189

Es wird manchmal behauptet, dass C++ 11/14 Ihnen einen Leistungsschub bringen kann, selbst wenn Sie nur C++ 98 Code kompilieren. Die Begründung erfolgt normalerweise in der Bewegungssemantik, da in einigen Fällen die rvalue-Konstruktoren automatisch erzeugt werden oder jetzt Teil der AWL sind. Jetzt frage ich mich, ob diese Fälle zuvor schon von RVO oder ähnlichen Compiler-Optimierungen behandelt wurden.Kann modernes C++ Ihnen Leistung kostenlos bringen?

Meine Frage ist dann, wenn Sie mir ein aktuelles Beispiel für einen Teil von C++ 98 Code geben könnten, der ohne Änderung schneller mit einem Compiler läuft, der die neuen Sprachfunktionen unterstützt. Ich verstehe, dass ein standardkonformer Compiler nicht erforderlich ist, um die Elision zu kopieren, und gerade deshalb könnte die Bewegungssemantik Geschwindigkeit bringen, aber ich würde gerne einen weniger pathologischen Fall sehen, wenn Sie so wollen.

EDIT: Nur um klar zu sein, ich frage nicht, ob neue Compiler schneller als alte Compiler sind, aber eher, wenn es Code gibt, wobei -std = C++ 14 zu meinen Compiler-Flags es schneller laufen würde (vermeiden Sie Kopien , aber wenn man mit etwas anderem außer bewegen Semantik kommen kann, würde mich interessieren, auch)

+3

Denken Sie daran, dass beim Erstellen eines neuen Objekts mit einem Kopierkonstruktor die Elisions- und Rückgabewertoptimierung durchgeführt wird. In einem Kopierzuweisungsoperator gibt es jedoch keine Kopiereliminierung (wie kann es sein, da der Compiler nicht weiß, was mit einem bereits konstruierten Objekt geschehen soll, das kein temporäres Objekt ist). Daher gewinnt C++ 11/14 in diesem Fall, indem es Ihnen die Möglichkeit gibt, einen Verschiebezuweisungsoperator zu verwenden. Über Ihre Frage denke ich jedoch nicht, dass C++ 98-Code schneller sein sollte, wenn er von einem C++ 11/14-Compiler kompiliert wird, vielleicht ist er schneller, weil der Compiler neuer ist. – vsoftco

+24

Auch Code, der die Standardbibliothek verwendet, ist potenziell schneller, selbst wenn Sie ihn vollständig kompatibel mit C++ 98 machen, da in C++ 11/14 die zugrunde liegende Bibliothek, wenn möglich, eine interne Semantik verwendet. So wird Code, der in C++ 98 und C++ 11/14 identisch aussieht, im letzteren Fall (möglicherweise) schneller, wenn Sie die Standard-Bibliotheksobjekte wie Vektoren, Listen usw. verwenden und die Verschiebungssemantik einen Unterschied macht. – vsoftco

+1

@vsoftco, Das ist die Art von Situation, auf die ich anspielte, aber ich konnte kein Beispiel finden: Von dem, an was ich mich erinnere, wenn ich den Kopierkonstruktor definieren muss, wird der Move-Konstruktor nicht automatisch generiert mit sehr einfachen Klassen, in denen RVO, glaube ich, immer funktioniert. Eine Ausnahme könnte in Verbindung mit den STL-Containern stehen, wo die rvalue-Konstruktoren vom Bibliotheksimplementierer erzeugt werden (was bedeutet, dass ich im Code nichts ändern muss, um Moves zu verwenden). – alarge

Antwort

208

Ich kenne 5 allgemeine Kategorien, bei denen die Neukompilierung eines C++ 03-Compilers als C++ 11 unbegrenzte Leistungssteigerungen verursachen kann, die praktisch nichts mit der Qualität der Implementierung zu tun haben. Dies sind alle Variationen der Bewegungssemantik.

std::vector umverteilen

struct bar{ 
    std::vector<int> data; 
}; 
std::vector<bar> foo(1); 
foo.back().data.push_back(3); 
foo.reserve(10); // two allocations and a delete occur in C++03 

jedes Mal die foo ‚s Puffer umverteilt wird in C++ 03 es jedes vector in bar kopiert.

In C++ 11 verschiebt es stattdessen die bar::data s, die im Grunde frei ist.

In diesem Fall beruht dies auf Optimierungen innerhalb des std Containers vector. In jedem Fall unten ist die Verwendung von std Containern nur, weil sie C++ Objekte sind, die eine effiziente move Semantik in C++ 11 "automatisch" haben, wenn Sie Ihren Compiler aktualisieren. Objekte, die es nicht blockieren, die einen std Container enthalten, erben auch die automatisch verbesserten move-Konstruktoren.

NRVO Ausfall

Wenn NRVO (return genannt Wertoptimierung) ausfällt, in C++ 03 es wieder auf Kopie fällt, auf C 11 ++ es wieder Bewegung fällt. Ausfälle von NRVO sind einfach:

std::vector<int> foo(int count){ 
    std::vector<int> v; // oops 
    if (count<=0) return std::vector<int>(); 
    v.reserve(count); 
    for(int i=0;i<count;++i) 
    v.push_back(i); 
    return v; 
} 

oder sogar:

std::vector<int> foo(bool which) { 
    std::vector<int> a, b; 
    // do work, filling a and b, using the other for calculations 
    if (which) 
    return a; 
    else 
    return b; 
} 

Wir haben drei Werte - den Rückgabewert, und zwei verschiedene Werte innerhalb der Funktion. Elision ermöglicht, dass die Werte innerhalb der Funktion mit dem Rückgabewert "zusammengeführt" werden, aber nicht miteinander. Beide können nicht mit dem Rückgabewert zusammengeführt werden, ohne miteinander zu verschmelzen.

Das grundlegende Problem ist, dass NRVO Elision ist zerbrechlich, und Code mit Änderungen nicht in der Nähe der return Website kann plötzlich massive Leistungsreduzierungen an dieser Stelle ohne Diagnose emittiert haben. In den meisten NRVO-Fehlerfällen endet C++ 11 mit einer move, während C++ 03 mit einer Kopie endet.

Rückgabe eines Funktionsargument

Elision ist auch hier unmöglich:

std::set<int> func(std::set<int> in){ 
    return in; 
} 

in C++ 11 dieses billig ist: in C++ 03 gibt es keine Möglichkeit, die Kopie zu vermeiden. Argumente für Funktionen können nicht mit dem Rückgabewert verknüpft werden, da die Lebensdauer und die Position des Parameters und des Rückgabewerts vom aufrufenden Code verwaltet werden.

Allerdings kann C++ 11 von einem zum anderen wechseln. (In einem weniger Spielzeugbeispiel könnte etwas an der set getan werden).

push_back oder insert

Schließlich elision in Behälter geschieht nicht aber C++ 11 Überlastungen rvalue bewegen Einsatz Operatoren, die Kopien spart.

struct whatever { 
    std::string data; 
    int count; 
    whatever(std::string d, int c):data(d), count(c) {} 
}; 
std::vector<whatever> v; 
v.push_back(whatever("some long string goes here", 3)); 

in C++ 03 eine temporäre whatever erstellt wird, dann wird es in den Vektor v kopiert. 2 std::string Puffer werden zugeordnet, jeder mit identischen Daten und einer wird verworfen. In C++ 11 wird eine temporäre whatever erstellt. Die whatever&&push_back Überlastung dann move s, dass temporäre in den Vektor v. Ein std::string Puffer wird zugewiesen und in den Vektor verschoben. Ein leerer std::string wird verworfen.

Zuordnung

Stolen von @ Jarod42 Antwort unten.

Elision kann nicht mit Zuweisung auftreten, aber move-from kann.

std::set<int> some_function(); 

std::set<int> some_value; 

// code 

some_value = some_function(); 

hier some_function gibt einen Kandidaten aus elide, sondern weil es nicht verwendet wird, um ein Objekt direkt zu konstruieren, ist es nicht elided werden kann. In C++ 03 führt das obige dazu, dass der Inhalt des temporären Objekts in some_value kopiert wird. In C++ 11 wird es in some_value verschoben, was grundsätzlich frei ist.


Für die volle Wirkung der oben genannten, müssen Sie einen Compiler, der für Sie unterwegs und Zuweisungs synthetisiert.

MSVC 2013 implementiert Move-Konstruktoren in std Containern, jedoch nicht Move-Konstruktoren für Ihre Typen.

Also Typen, die std::vector s und ähnliche enthalten, erhalten solche Verbesserungen in MSVC2013 nicht, aber beginnen, sie in MSVC2015 zu erhalten.

clang und gcc haben seit langem implizite Move-Konstruktoren implementiert.Intels 2013-Compiler unterstützt die implizite Generierung von Move-Konstruktoren, wenn Sie -Qoption,cpp,--gen_move_operations übergeben (sie tun dies standardmäßig nicht, um mit MSVC2013 kompatibel zu sein).

+0

Ist es möglich, etwas Ähnliches ohne STL-Container zu tun, oder werden automatisch generierte Move-Konstruktoren und solche für Copy Elision immer in Frage kommen? – alarge

+1

@alarge ja.Damit ein Verschiebungskonstruktor um ein Vielfaches effizienter ist als ein Kopierkonstruktor, muss er normalerweise Ressourcen verschieben, anstatt sie zu kopieren. Ohne eigene Move-Konstruktoren zu schreiben (und nur ein C++ 03-Programm neu zu kompilieren), werden die 'std'-Bibliothekscontainer alle mit' move'-Konstruktoren "kostenlos" aktualisiert, und (wenn Sie sie nicht blockiert haben) Konstrukte, die die Objekte (und die Objekte) verwenden, beginnen in einer Anzahl von Situationen mit der Konstruktion der freien Bewegung. Viele dieser Situationen werden durch Elision in C++ 03 abgedeckt: nicht alle. – Yakk

+5

Das ist eine schlechte Optimiererimplementierung. Da die zurückgegebenen Objekte mit unterschiedlichen Namen keine überlappende Lebensdauer haben, ist RVO theoretisch immer noch möglich. –

42

, wenn Sie so etwas wie:

std::vector<int> foo(); // function declaration. 
std::vector<int> v; 

// some code 

v = foo(); 

Sie ++ eine Kopie in C bekam 03, während Du hast einen Umzug in C++ 11 bekommen. so haben Sie freie Optimierung in diesem Fall.

+4

@Yakk: Wie Kopie Elision tritt in Zuweisung? – Jarod42

+2

@ Jarod42 Ich glaube auch, dass Kopieren Elision ist nicht möglich in einer Aufgabe, da die linke Seite ist bereits konstruiert und es gibt keinen vernünftigen Weg für einen Compiler zu wissen, was mit den "alten" Daten nach dem Stehlen der Ressourcen aus dem tun rechte Seite. Aber vielleicht liege ich falsch, ich würde gerne einmal und für immer die Antwort herausfinden. Kopieren Elision macht Sinn, wenn Sie Konstrukt kopieren, da das Objekt "frisch" ist und es kein Problem gibt, zu entscheiden, was mit den alten Daten geschehen soll. Soweit ich weiß, ist dies die einzige Ausnahme: "Zuordnungen können nur basierend auf der Als-ob-Regel gelöscht werden" – vsoftco

+2

Guter C++ 03-Code hat in diesem Fall bereits eine Verschiebung über 'foo() vorgenommen. Swap (v) ; ' –