Nach meinem Verständnis ist der Compiler nicht verpflichtet, den Funktionsaufruf einer Inline-Funktion durch seinen Body zu ersetzen, sondern wird dies tun, wenn dies möglich ist. Das brachte mich zum Nachdenken - warum haben wir das Inline-Wort, wenn das der Fall ist? Warum nicht alle Funktionen standardmäßig inline machen und den Compiler herausfinden lassen, ob er die Aufrufe durch den Funktionshauptteil ersetzen kann oder nicht?
c++
language-design
optimization
compilation
EpsilonVector
quelle
quelle
Antworten:
inline
ist von C; es war nicht neu in C ++.Es gibt C-Schlüsselwörter (
register
undinline
), mit denen der Programmierer die Codeoptimierung unterstützen kann. Diese werden heutzutage im Allgemeinen ignoriert, da Compiler die Registerzuweisung und die Entscheidung über Inline-Funktionen verbessern können (tatsächlich kann ein Compiler eine Funktion zu verschiedenen Zeiten inline oder nicht inline setzen). Die Codegenerierung auf modernen Prozessoren ist weitaus komplizierter als bei den deterministischeren Prozessoren, die bei Ritchie als Erfinder von C üblich waren.In C ++ bedeutet das Wort jetzt, dass es mehrere identische Definitionen haben kann und in jeder Übersetzungseinheit definiert werden muss, die es verwendet. (Mit anderen Worten, Sie müssen sicherstellen, dass es eingebettet werden kann.) Sie können
inline
problemlos eine Funktion in einer Kopfzeile haben, und die in einer Klassendefinition definierten Elementfunktionen werden automatisch wirksaminline
.quelle
inline
.inline
in C ++ standardisiert war, obwohl es bereits als Herstellererweiterung in C verfügbar war.inline
C99 und höher sich von den Regeln fürinline
C ++ unterscheiden.Ursprünglich
inline
war ein sehr starker Hinweis, dass Aufrufe der Funktion inline sein sollten.Der einzige garantierte Effekt von
inline
ist jedoch, dass eine Funktion in mehreren Übersetzungseinheiten (effektiv identisch) definiert werden kann, z. B., dass Sie die Definition in eine Header-Datei einfügen.Heutzutage sind einige Compiler sehr daran interessiert, dem Inlining-Hinweis zu folgen, z. B. g ++. Und einige Compiler nehmen es weniger ernst, z. B. Visual C ++. Aber alle müssen sich an die Garantie halten.
Es ist bedauerlich, dass diese beiden Bedeutungen - Optimierungshinweis und was wir als verwerfbare Definition auf Linker-Ebene bezeichnen - mit demselben Schlüsselwort verknüpft sind, da dies bedeutet, dass Sie praktisch keine ohne die andere haben können.
Es ist auch bedauerlich, dass
inline
(oder besser, ein separates Schlüsselwort zur verwerfbaren Definition) ¹nicht auf Daten angewendet werden kann .Der Bedarf an verwerfbaren Daten auf Linker-Ebene hat zugenommen, da reine Header-Module immer beliebter werden. Beispielsweise sind viele Boost-Unterbibliotheken nur für den Header bestimmt.
Für Daten können Sie jedoch einen kleinen Trick mit Vorlagen anwenden. Definieren Sie es in einer Klassenvorlage, geben Sie einen
typedef
Vorlagenparameter anvoid
(oder was auch immer). Das liegt daran, dass die One Definition Rule eine bestimmte Ausnahme für Vorlagen macht.Anmerkungen:
¹
inline
Variablen werden in C ++ 17 unterstützt .quelle
inline
.Warum nicht alle Funktionen standardmäßig inline machen? Weil es ein technischer Kompromiss ist. Es gibt mindestens zwei Arten der "Optimierung": Beschleunigen des Programms und Verringern der Größe (Speicherbedarf) des Programms. Inlining beschleunigt die Dinge im Allgemeinen. Der Funktionsaufruf-Overhead entfällt, und es wird vermieden, Parameter vom Stapel zu ziehen. Dies vergrößert jedoch auch den Speicherbedarf des Programms, da jeder Funktionsaufruf jetzt durch den vollständigen Code der Funktion ersetzt werden muss. Denken Sie daran, dass die CPU häufig verwendete Speicherbereiche in einem Cache auf der CPU speichert, um einen ultraschnellen Zugriff zu ermöglichen. Wenn Sie das Speicherimage des Programms groß genug machen, kann Ihr Programm den Cache nicht effizient nutzen, und im schlimmsten Fall kann das Inlining Ihr Programm tatsächlich verlangsamen.
quelle
Um „Inline“ zu verstehen, muss man die Geschichte verstehen und wissen, wie das Leben vor 20 (und 30) Jahren war.
Wir haben Code auf Computern mit wenig Arbeitsspeicher geschrieben, daher war es einem Compiler nicht möglich, den gesamten Code, aus dem ein Programm bestand, auf einmal zu verarbeiten. Der Compiler war auch sehr langsam, sodass Sie keinen Code neu kompilieren mussten, der sich nicht geändert hatte - 24 Stunden (auf einem Computer, der mehr als ein Top-End-Auto kostete), um den gesamten Code neu zu kompilieren, war für einige Projekte, die ich durchgeführt habe, normal arbeitete an.
Daher wurde jede Codedatei separat in eine Objektdatei kompiliert. Jede Objektdatei begann mit einer Liste aller darin enthaltenen Funktionen, zusammen mit der „Adresse“ der Funktion. Eine Objektdatei enthielt auch eine Liste aller aufgerufenen Funktionen in anderen Objektdateien sowie den Speicherort des Aufrufs.
Ein Linker würde zuerst alle Objektdateien lesen und eine Liste aller von ihm exportierten Funktionen zusammen mit der Datei, in der sie sich befanden, und ihrer Adresse erstellen. Es würde dann alle Objektdateien erneut lesen und sie in die Programmdatei ausgeben, während alle "externen" Funktionsaufrufe mit der Adresse der Funktion aktualisiert würden.
Der Linker hat den vom Compiler erstellten Maschinencode in keiner anderen Weise geändert oder optimiert, als um Verweise auf externe Funktionsaufrufe zu korrigieren. Der Linker war Teil des Betriebssystems und älter als die meisten Compiler. Wenn Leute einen neuen Compiler schrieben, brauchten sie ihn, um mit aktuellen Linkern zu arbeiten und um in der Lage zu sein, auf aktuelle Objektdateien zu verlinken, sonst könnten keine Systemaufrufe gemacht werden.
Der Compiler hat den Code immer nur in der „.c“ - oder „.cpp“ -Datei gesehen, die er zusammen mit allen enthaltenen Header-Dateien kompilierte. Daher konnte keine Optimierung basierend auf Code in anderen „.c“ - oder „.cpp“ -Dateien durchgeführt werden.
Mit dem Schlüsselwort "inline" konnte der Hauptteil einer Funktion (Methode) in einer Header-Datei definiert werden, sodass der Compiler den Code der Funktion beim Kompilieren des aufrufenden Codes verwenden konnte. Angenommen, Sie hätten eine Auflistungsklasse in einer anderen .cpp-Datei definiert. Diese Klasse hätte eine "isEmpty" -Methode, die eine Codezeile enthielte. Anstelle eines Funktionsaufrufs würde das resultierende Programm erheblich beschleunigt wurde der Funktionsaufruf durch diese eine Zeile ersetzt.
Das Schlüsselwort "Inline" wurde zu der Zeit als "billig und einfach" angesehen, um die Kapselung von Daten zu ermöglichen und gleichzeitig die Kosten für Funktionsaufrufe zu vermeiden. Ohne dieses Schlüsselwort hätten viele Programmierer nur Zugriff auf die privaten Felder des Objekts. (Makros, bei denen ein viel schlechterer Weg war, Code zu „inlinen“, als dies zu der Zeit üblich war.)
Heutzutage führen „Linker“ eine Menge Code-Optimierung durch und werden in der Regel von einem Team als Compiler geschrieben. Der Compiler überprüft oft nur, ob der Code korrekt ist, und „komprimiert“ ihn, sodass der Linker die meiste Aufgabe der Maschinencodeerstellung übernimmt.
quelle
Mal sehen, was der Standard sagt (hervorgehobene wichtige Teile in Fettdruck):
Wenn Sie also sicher sein möchten, sollten Sie die Dokumentation Ihres Compilers lesen.
Alles einzufügen ist eine schlechte Idee, da es zu einer Menge dupliziertem Maschinencode führen kann ...
Also musst du wissen:
quelle
inline
indem ich das Wissen wiederhole, da dem OP ein Teil zu fehlen scheint, was der Hauptgrund ist, warum er den Punkt nicht versteht. Um einen Punkt zu bekommen, muss er verstehen, was unter diesem Punkt liegt ...Lassen Sie mich einen guten Grund für die Verwendung des Inline-Schlüsselworts nennen.
Auf einem eingebetteten System, z. B. einem Ticketdrucker oder einem ähnlichen kleineren System. Der Prozessor ist sehr begrenzt und ein Funktionsaufruf (Vorbereiten von Funktionsparametern auf dem Stack, Aufrufen, Abrufen von Parametern vom Stack und Zurücksetzen der Antwort usw.) kann neben der eigentlichen Funktion mehrere ms dauern, bis er ausgeführt wird.
Nehmen wir an, die Anrufzeit beträgt ca. 60 ms (nur zum Aufrufen, nicht für die eigentliche Funktion) und Sie haben 50 Iterationen (Schleifen- oder iterative Aufrufe in einem Baum).
Die Zeit, um von diesem Funktionsaufruf einfach vor und zurück zu gehen, beträgt 60 * 50 = 3000 (3 Sekunden).
Wenn Sie den Speicher haben, würden Sie definitiv eine Inline-Aktion ausführen, um 3 Sekunden zu sparen.
Inline werden also im Grunde genommen verwendet, wenn Sie Ausführungsgeschwindigkeit benötigen. In einigen Projekten, an denen ich beteiligt war, war die Aufrufzeit länger als die Ausführungszeit, eine klassische Situation bei der Verwendung von Inline.
quelle