Warum entsteht Overhead beim Aufrufen von Funktionen?

Lesezeit: 4 Minuten

Benutzer-Avatar
Kamela Franco

Oft spricht man vom Aufruf von Funktionen, die eine bestimmte Menge an erzeugen Overhead, oder eine unausweichliche Reihe zusätzlicher Bedenken und Umstände in einem Programm. Kann man das besser erklären und mit einem ähnlichen Programm ohne den Funktionsaufruf vergleichen?

  • mögliches Duplikat von Was passiert in der Assemblersprache, wenn Sie eine Methode/Funktion aufrufen?

    – Michael Petsch

    3. August 2015 um 4:17 Uhr

  • Was sind “diese Routinen” beziehst du dich?

    – Jens

    3. August 2015 um 4:23 Uhr

  • Inline ist nicht immer möglich. Rekursive Funktionen, virtuelle Funktionen und Funktionszeiger sind Beispiele. (manchmal können sie immer noch inliniert werden, aber nicht im allgemeinen Fall)

    – Mystisch

    3. August 2015 um 4:23 Uhr

  • Es ist auch wichtig zu beachten, dass Eingabeargumente manchmal konstante Werte sind (fest codierte Parameter, wie z. B. eine Schleifenanzahl, die zur Kompilierzeit bekannt sind, aber je nach Aufrufseite unterschiedlich sind). inlineDas Erstellen solcher Funktionen macht diese konstanten Werte für den Compiler verfügbar, was eine aggressivere Optimierung ermöglicht.

    – Benutzer3528438

    3. August 2015 um 12:39 Uhr


Benutzer-Avatar
François Zard

Dies hängt von Ihren Compilereinstellungen und der Art und Weise ab, wie der Code optimiert wird. Einige Funktionen sind eingebettet. Andere sind es nicht. Es hängt normalerweise davon ab, ob Sie auf Größe oder Geschwindigkeit optimieren.

Im Allgemeinen verursacht der Aufruf einer Funktion aus zwei Gründen Verzögerungen:

  • Das Programm muss sich an einer zufälligen Stelle im Speicher einklinken, an der Ihr Funktionscode beginnt. Dazu muss er die aktuelle Cursorposition in einem Stack speichern, damit er weiß, wohin er zurückkehren muss. Dieser Prozess verbraucht mehr als einen CPU-Zyklus.

  • Abhängig von Ihrer CPU-Architektur kann es eine Pipeline geben, die parallel zu Ihrer aktuellen Befehlsausführung die nächsten paar Befehle aus dem Speicher in den CPU-Cache holt. Dies soll die Ausführungsgeschwindigkeit beschleunigen. Wenn Sie eine Funktion aufrufen, hängt sich der Cursor an eine völlig andere Adresse und alle zwischengespeicherten Anweisungen werden aus der Pipeline gelöscht. Dies führt zu weiteren Verzögerungen.

  • 1) und 2) sind nicht üblich. Moderne Architekturen können die Ausführung von Code ziemlich gut vorhersagen. Beachten Sie auch, dass es sich um einen Funktionsaufruf handelt stets vorhersagbar, da Sie wissen, wohin die Ausführung geht, und Sie daher Code vorab abrufen und die Pipeline füllen können. Da gibt es keine Verzögerungen.

    – Jens

    3. August 2015 um 4:29 Uhr


  • Sie sind für den x86-Bogen AFAIK

    – François Zard

    3. August 2015 um 4:50 Uhr

  • Nein, x86 CALL ist heutzutage etwa ein Zyklus.

    – Jens

    3. August 2015 um 11:12 Uhr

  • Es ist auch wichtig, Konventionen anzurufen. Angerufene- und Anrufer-Clobbered-Register müssen gespeichert und wiederhergestellt werden. Wenn Sie Stapelanpassungen und Rahmenzeiger hinzufügen, ist es oft billiger, kleine Funktionen einzubetten, selbst wenn Sie die Größe optimieren.

    – Technosaurus

    9. März 2018 um 23:10 Uhr


Benutzer-Avatar
Jens

Siehe auch hier und hier für eine Diskussion darüber, wann Inlining sinnvoll ist.

  • Einfügen

    Generell kann man den Compiler nur anregen inline eine Funktion, aber der Compiler könnte anders entscheiden. Visual Studio bietet eine eigene forceinline Stichwort aber. Einige Funktionen können nicht inliniert werden, z. B. wenn sie rekursiv sind oder wenn die Zielfunktion zur Kompilierzeit nicht bestimmt werden kann (Aufrufe über Funktionstabellen, virtuelle Funktionsaufrufe in C++).

    Ich schlage vor, Sie vertrauen dem Compiler, ob eine Funktion inliniert werden soll oder nicht. Wenn Sie Ihren Code wirklich einbetten möchten, sollten Sie stattdessen ein Makro verwenden.

  • Overhead

    Der Speicheraufwand ist minimal, wenn Sie Funktionen verwenden, da Sie keinen Code duplizieren. Inline-Code wird in die Aufrufsite dupliziert. Der Leistungsaufwand ist heutzutage vernachlässigbar, da moderne Architekturen dies sind Ja wirklich gutes Vorhersagen und Aufrufen mit nur etwa 1-2 Zyklen Overhead.

Benutzer-Avatar
Keith Nicholas

Die Funktionen können sein inlinedaber die Norm (meistens) ist, dass sich Funktionen an einer bestimmten Adresse befinden und an die Funktion übergebene Werte auf den Stapel gelegt werden und das Ergebnis dann auf den Stapel gelegt und zurückgegeben wird.

  • Fast alle Architekturen verwenden registerbasierte Aufrufkonventionen, wenn der Code optimiert wird, um Daten so nah wie möglich an der Ausführung zu halten. Rückgabewerte werden immer in einem Register zurückgegeben.

    – Jens

    3. August 2015 um 4:31 Uhr


  • einige sind, aber ich halte das für eine Optimierung. Ich mache viel Embedded-Programmierung, und viele davon haben sehr wenig Registerplatz, um das zu tun.

    – Keith Nicholas

    3. August 2015 um 4:35 Uhr

  • @ Jens x86 verwendet immer noch die Stack-basierte Aufrufkonvention und die meisten anderen Mikrocontroller verwenden immer noch den Stack, um Parameter zu übergeben, da keine Register vorhanden sind. Rückgabewerte werden nur dann durch Register geleitet, wenn sie klein genug sind, um in 1 oder 2 Register zu passen, andernfalls werden sie über den Stack zurückgegeben

    – phuklv

    3. August 2015 um 4:41 Uhr

Funktioniert bestimmt kann inline sein, wenn einige Bedingungen erfüllt sind, aber sie sind definitiv nicht immer inline. Meistens erzeugt der Aufruf einer Funktion einen echten, nicht eingebetteten Funktionsaufruf. Ein Funktionsaufruf ist mit einigen zusätzlichen Ausgaben verbunden, wie z

  • Vorbereiten von Parametern für die Funktion gemäß der Aufrufkonvention der Funktion
  • Empfangen des Rückgabewerts der Funktion
  • Funktionsprolog- und Epilogcode, verantwortlich für lokale Speicherverwaltung, Parameterspeicherverwaltung und Registerwerterhaltung
  • Die Funktion kann einige CPU-Register überlasten, wodurch ihre Verwendung im aufrufenden Code unterbrochen und somit Optimierungen behindert werden
  • Weniger CPU-Cache-freundliches und virtuelles Speicher-freundliches Verhalten von Code, der auf nichtlineare Weise ausgeführt wird

All dies führt zu Overhead, der wahrscheinlich nicht vorhanden wäre, wenn der Funktionsrumpf inline in den aufrufenden Code eingebettet wäre.

1158100cookie-checkWarum entsteht Overhead beim Aufrufen von Funktionen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy