Ich habe dieses einfache Beispiel, gegen das ich getestet habe, und mir ist aufgefallen, dass gcc-Optimierungen (-O3) nicht so gut sind wie klingende, wenn operator new beteiligt ist. Ich frage mich, was könnte das Problem sein und wenn es möglich ist, gcc zu zwingen, mehr optimierten Code irgendwie zu produzieren?
%Vor%Beispiel oben ist nur eine einfache Version des Codes, den ich am Anfang getestet habe, aber es zeigt immer noch den Unterschied zwischen gcc / clang. Ich habe auch den Assembler-Code überprüft und es gibt keinen großen Unterschied in der Größe, aber definitiv in der Leistung. Auf der anderen Seite macht calling etwas, was nicht erlaubt ist?
Wenn wir diesen Code in godbolt einbinden, können wir sehen, dass clang
den Code darauf hin optimiert:
, während gcc
diese Optimierung nicht durchführt. Die Frage ist also eine gültige Optimierung? Folgt dies dem as-if rule
, das im Entwurf des C ++ - Standards vermerkt ist Abschnitt 1.9
Programmausführung was sagt ( Hervorhebung meins ):
Die semantischen Beschreibungen in dieser Internationalen Norm definieren a parametrisierte nichtdeterministische abstrakte Maschine. Diese Internationale Standard stellt keine Anforderungen an die Konformitätsstruktur Implementierungen. Insbesondere müssen sie nicht kopieren oder emulieren Struktur der abstrakten Maschine. Vielmehr konforme Implementierungen sind erforderlich, um (nur) das beobachtbare Verhalten des Abstrakten nachzuahmen Maschine wie unten erklärt. 5
wo die Anmerkung 5
sagt:
Diese Bestimmung wird manchmal als "Als-ob" -Regel bezeichnet, weil ein die Umsetzung ist frei, jede Anforderung davon zu ignorieren International Standard, solange das Ergebnis ist als ob die Anforderung wurde gehorcht, soweit sich aus dem Observablen ergibt Verhalten des Programms. Zum Beispiel ein tatsächlicher Implementierungsbedarf Bewerte einen Teil eines Ausdrucks nicht, wenn er folgern kann, dass sein Wert ist nicht verwendet und keine Nebenwirkungen, die das beobachtbare Verhalten beeinflussen das Programm wird produziert.
Da new
eine Ausnahme auslösen könnte, die ein beobachtbares Verhalten hätte, da dies den Rückgabewert des Programms verändern würde.
R.MartinhoFernandes argumentiert, dass es Implementierungsdetails sind, wann eine Ausnahme ausgelöst werden soll und daher clang
entscheiden könnte, dass dieses Szenario keine Ausnahme verursachen würde und daher der Aufruf von new
nicht gegen as-if rule
verstoßen würde. Das scheint mir ein vernünftiges Argument zu sein.
aber als T.C. weist darauf hin:
Ein Ersatz globaler Operator neu könnte in einer anderen Übersetzungseinheit definiert worden sein
Casey lieferte ein Beispiel, das zeigt, dass auch dann, wenn clang
sieht, dass es einen Ersatz gibt, diese Optimierung trotzdem ausgeführt wird, obwohl es Nebenwirkungen gibt. Das scheint also eine übermäßig aggressive Optimierung zu sein.
Der Grund dafür ist, dass es keine Regel gibt, wie viel Speicher eine Maschine haben darf, noch bietet die Sprache eine Möglichkeit, die Menge an zugewiesenem oder freiem Speicher zu untersuchen (obwohl POSIX mallinfo definiert). Hier simulieren wir Ihr Programm auf einer abstrakten Maschine mit unendlicher Speichermaschine, wo die Zuweisung kontinuierlich gelingt. Oder zumindest unendlicher Speicher für die Zwecke der Zuweisungen in dieser Schleife, aber nicht konsistent für ein ganzes Programm. Jedenfalls sind mir zwei gute Einwände bekannt.
Überlegen Sie zuerst, ob es Malloc anstelle von Operator New war. Die C99-Spezifikation besagt:
Die malloc-Funktion weist Speicherplatz für ein Objekt zu, dessen Größe durch die Größe angegeben wird und dessen Wert nicht definiert ist. Die malloc-Funktion gibt entweder einen Nullzeiger oder einen Zeiger auf den zugewiesenen Speicherplatz zurück.
Das Kompilieren von malloc (), um immer erfolgreich zu sein, scheint dieser Spezifikation zu entsprechen. Aber was ist, wenn Sie es öfter aufrufen, als wir tatsächlich einen Zeiger für die Schleife erstellen können und diese nur dann verlassen, wenn sie versagt? Ein möglicher Ausweg besteht darin, zu beachten, dass es in der abstrakten Maschinendefinition keine Regel gibt, dass ein 64-Bit-Zeiger nur 2 64 mögliche Werte enthalten kann, nur dass es keine Möglichkeit gibt, Werte außerhalb davon zu konstruieren Angebot. Es scheint, dass die Implementierung solche Dinge nach Belieben erzeugen kann. Ich persönlich finde diese Antwort unbefriedigend.
Denken Sie daran, dass wir auch Dinge wie "T *t1 = new T; T *t2 = (T*)rand();"
optimieren, indem wir annehmen, dass t1
möglicherweise nicht alias t2
ist. Es spielt keine Rolle, ob Rand die richtige Adresse ausgewählt hat oder ob Sie über den gesamten Adressraum iteriert haben. Sobald wir zeigen, dass die Adresse von t1 nicht in t2 einfloss, sollten wir daraus schließen können, dass sie sich auf verschiedene Objekte beziehen. Während ich möchte, dass der Standard funktioniert, und so arbeiten Compiler, ist mir keine Standardisierung bekannt, um diese Position zu unterstützen. Dies wird wahrscheinlich das Thema eines zukünftigen Papiers werden.
Zweitens ist der Operator new nicht malloc, sondern eine ersetzbare Funktion. Wie in der Antwort von Casey angedeutet, beabsichtigen wir, die Regeln in N3664
Es scheint, dass clang die Speicherzuweisungen gemäß den geänderten Regeln in N3664 Clarifying Memory Allocation , die in C ++ 14 integriert wurde. N3664 ermöglicht es, die Anzahl der Aufrufe von Zuweisungs- / Freigabe-Funktionen zu verringern, indem Zuordnungen zusammengeführt oder Zuordnungen ganz eliminiert werden.
Tags und Links c++ gcc clang c++11 compiler-optimization