Java, wie teuer ist ein Methodenaufruf

81

Ich bin ein Anfänger und habe immer gelesen, dass es schlecht ist, Code zu wiederholen. Es scheint jedoch, dass Sie normalerweise zusätzliche Methodenaufrufe benötigen, um dies nicht zu tun. Angenommen, ich habe die folgende Klasse

public class BinarySearchTree<E extends Comparable<E>>{
    private BinaryTree<E> root;
    private final BinaryTree<E> EMPTY = new BinaryTree<E>();
    private int count;
    private Comparator<E> ordering;

    public BinarySearchTree(Comparator<E> order){
        ordering = order;
        clear();
    }

    public void clear(){
        root = EMPTY;
        count = 0;
    }
}

Wäre es für mich optimaler, die beiden Zeilen meiner clear () -Methode einfach zu kopieren und in den Konstruktor einzufügen, anstatt die eigentliche Methode aufzurufen? Wenn ja, wie viel Unterschied macht es? Was wäre, wenn mein Konstruktor 10 Methodenaufrufe ausführen würde, wobei jeder einfach eine Instanzvariable auf einen Wert setzt? Was ist die beste Programmierpraxis?

jhlu87
quelle
4
Aber warten Sie, wenn Sie die Methode jetzt aufrufen, werfen wir einen ZWEITEN Methodenaufruf ein, absolut kostenlos! Zahlen Sie nur Versandkosten! Aber im Ernst. Es gibt Overhead bei Methodenaufrufen, genauso wie Overhead beim Laden von mehr Code. Irgendwann wird einer teurer als der andere. Die einzige Möglichkeit, dies festzustellen, besteht darin, Ihren Code zu vergleichen.
Marc B
11
vorzeitige Optimierungszitate in 3 ... 2 ... 1
21
Ich sehe keinen Grund für die Ablehnung - der Typ stellt eine absolut legitime Frage. Es mag eine offensichtliche Antwort auf einige sein, aber das macht es nicht zu einer schlechten Frage!
Michael Berry
3
In der Tat ist es eine sehr legitime Frage, der einzige Grund für eine Ablehnung könnte sein, wenn es ein genaues Duplikat gibt.
Arafangion
1
Ja, tut mir leid, wenn es offensichtlich ist, aber ich lerne selbst und bin erst seit ein paar Monaten dabei. Einige Dinge, die ich im Online-Beispielcode gesehen habe, haben sich als nicht empfehlenswert erwiesen, daher möchte ich sie nur noch einmal überprüfen.
Jhlu87

Antworten:

74

Wäre es für mich optimaler, die beiden Zeilen meiner clear () -Methode einfach zu kopieren und in den Konstruktor einzufügen, anstatt die eigentliche Methode aufzurufen?

Der Compiler kann diese Optimierung durchführen. Und die JVM auch. Die vom Compiler-Writer und JVM-Autoren verwendete Terminologie lautet "Inline-Erweiterung".

Wenn ja, wie viel Unterschied macht es?

Messe Es. Oft werden Sie feststellen, dass es keinen Unterschied macht. Und wenn Sie glauben, dass dies ein Performance-Hotspot ist, suchen Sie am falschen Ort. Deshalb müssen Sie es messen.

Was wäre, wenn mein Konstruktor 10 Methodenaufrufe ausführen würde, wobei jeder einfach eine Instanzvariable auf einen Wert setzt?

Dies hängt wiederum vom generierten Bytecode und den von der Java Virtual Machine durchgeführten Laufzeitoptimierungen ab. Wenn der Compiler / die JVM die Methodenaufrufe einbinden kann, führt er die Optimierung durch, um den Aufwand für die Erstellung neuer Stapelrahmen zur Laufzeit zu vermeiden.

Was ist die beste Programmierpraxis?

Vorzeitige Optimierung vermeiden. Die beste Vorgehensweise besteht darin, lesbaren und gut gestalteten Code zu schreiben und dann für die Leistungs-Hotspots in Ihrer Anwendung zu optimieren.

Vineet Reynolds
quelle
Was ist ein guter Weg zum Benchmarking? Gibt es eine Software, die ich herunterladen kann, oder verwenden Sie einfach System.nanoTime () am Anfang und Ende und drucken Sie den Unterschied aus?
Jhlu87
System.nanoTime()oder System.currentTimeMillisist eine schlechte Art der Profilerstellung. Eine Liste der Profiler finden Sie in der Antwort auf diese Stackoverflow-Frage . Ich würde VisualVM empfehlen, da es jetzt mit dem JDK geliefert wird.
Vineet Reynolds
2
@ jhlu87: Ich denke, es fällt Ihnen sehr schwer, eine genaue Schätzung des Overheads eines Methodenaufrufs zu erhalten. Microbenchmarking ist sehr schwer zu korrigieren und selbst dann im großen Schema der Dinge im Allgemeinen nicht sehr nützlich. Lesen Sie dies .
ColinD
@VineetReynolds die verknüpfte Frage ist tot.
dim8
19

Was alle anderen über Optimierung gesagt haben, ist absolut wahr.

Aus Sicht der Leistung gibt es keinen Grund , die Methode zu integrieren. Wenn es sich um ein Leistungsproblem handelt, wird es von der JIT in Ihrer JVM eingebunden. In Java sind Methodenaufrufe so kostenlos, dass es sich nicht lohnt, darüber nachzudenken.

Davon abgesehen gibt es hier ein anderes Problem. Das heißt, es ist schlechter Programmier eine überschreibbar Methode aufrufen (dh eine , die nicht ist final, staticoder private) aus dem Konstruktor. (Effektives Java, 2. Aufl., S. 89, unter dem Titel "Entwurf und Dokument zur Vererbung oder Verbot").

Was passiert, wenn jemand eine Unterklasse von BinarySearchTreeaufgerufen hinzufügt LoggingBinarySearchTree, die alle öffentlichen Methoden mit Code überschreibt, wie:

public void clear(){
  this.callLog.addCall("clear");
  super.clear();
}

Dann LoggingBinarySearchTreewird das niemals konstruierbar sein! Das Problem ist, dass dies this.callLogder Fall ist, nullwenn der BinarySearchTreeKonstruktor ausgeführt wird, aber das clearaufgerufene ist das überschriebene, und Sie erhalten ein NullPointerException.

Beachten Sie, dass sich Java und C ++ hier unterscheiden: In C ++ ruft ein Konstruktor virtualder Oberklasse, der eine Methode aufruft, die in der Oberklasse definierte auf, nicht die überschriebene. Menschen, die zwischen den beiden Sprachen wechseln, vergessen dies manchmal.

Angesichts dessen denke ich, dass es in Ihrem Fall wahrscheinlich sauberer ist, die clearMethode beim Aufruf vom Konstruktor zu integrieren , aber im Allgemeinen sollten Sie in Java alle gewünschten Methodenaufrufe ausführen.

Daniel Martin
quelle
1
Ich glaube nicht, dass er nach Tipps für den Codierungsstil gefragt hat, sondern dass er wusste, ob ein Methodenaufruf teuer ist oder nicht
Asaf Mesika
3
Er fragte explizit: "Was ist die beste Programmierpraxis?" - In Bezug auf Best Practices ist dies absolut relevant.
Daniel Martin
2
Wenn Sie nur den letzten Satz nehmen, verlieren Sie den Kontext dieser Frage vollständig. Er möchte wissen, ob es teuer ist, Ihre große Methode in viele kleinere Methoden aufzuteilen, da ein Methodenaufruf einen Preis hat. Das Hinzufügen einer Antwort, die ein Anti-Muster zum Aufrufen einer nicht endgültigen Methode von einem Konstruktor beschreibt, zählt nicht als Antwort auf seine Frage als Ganzes. Oh und lesen Sie den Titel der Frage "Wie teuer ist ein Methodenaufruf"
Asaf Mesika
6

Ich würde es definitiv so lassen wie es ist. Was ist, wenn Sie die clear()Logik ändern ? Es wäre unpraktisch, alle Stellen zu finden, an denen Sie die beiden Codezeilen kopiert haben.

Marcelo
quelle
4

Im Allgemeinen (und als Anfänger bedeutet dies immer!) Sollten Sie niemals Mikrooptimierungen vornehmen, wie Sie sie in Betracht ziehen. Bevorzugen Sie immer die Lesbarkeit von Code gegenüber solchen Dingen.

Warum? Weil der Compiler / Hotspot diese Art von Optimierungen im Handumdrehen für Sie vornimmt und viele, viele mehr. Wenn Sie versuchen, Optimierungen in dieser Richtung vorzunehmen (wenn auch nicht in diesem Fall), werden Sie die Dinge wahrscheinlich langsamer machen. Hotspot versteht gängige Programmiersprachen. Wenn Sie versuchen, diese Optimierung selbst durchzuführen, wird es wahrscheinlich nicht verstehen, was Sie versuchen, sodass es nicht optimiert werden kann.

Es gibt auch viel höhere Wartungskosten. Wenn Sie anfangen, Code zu wiederholen, ist die Wartung viel aufwändiger, was wahrscheinlich viel mühsamer ist, als Sie vielleicht denken!

Abgesehen davon können Sie zu einigen Punkten in Ihrem Codierungsleben gelangen, an denen Sie Optimierungen auf niedriger Ebene vornehmen müssen. Wenn Sie diese Punkte jedoch erreichen, wissen Sie definitiv, wann die Zeit gekommen ist. Wenn Sie dies nicht tun, können Sie später jederzeit zurückkehren und bei Bedarf optimieren.

Michael Berry
quelle
3

Die beste Vorgehensweise ist, zweimal zu messen und einmal zu schneiden.

Sobald Sie Zeit für die Optimierung verschwendet haben, können Sie sie nie wieder zurückerhalten! (Messen Sie es also zuerst und fragen Sie sich, ob es sich lohnt, es zu optimieren. Wie viel Zeit sparen Sie tatsächlich?)

In diesem Fall führt die Java-VM wahrscheinlich bereits die Optimierung durch, über die Sie sprechen.

Arafangion
quelle
3

Die Kosten eines Methodenaufrufs sind die Erstellung (und Entsorgung) eines Stapelrahmens und einiger zusätzlicher Bytecode-Ausdrücke, wenn Sie Werte an die Methode übergeben müssen.

Andreas Dolk
quelle
1

Das Muster, dem ich folge, ist, ob diese fragliche Methode eine der folgenden Bedingungen erfüllen würde oder nicht:

  • Wäre es hilfreich, diese Methode außerhalb dieser Klasse verfügbar zu haben?
  • Wäre es hilfreich, diese Methode in anderen Methoden verfügbar zu haben?
  • Wäre es frustrierend, dies jedes Mal neu zu schreiben, wenn ich es brauchte?
  • Könnte die Vielseitigkeit der Methode durch die Verwendung einiger Parameter erhöht werden?

Wenn eine der oben genannten Aussagen zutrifft, sollte sie in eine eigene Methode eingepackt werden.

Peaches491
quelle
Es ist einfacher, diese Fragen nicht zu stellen und den verdammten Code bereits in seine eigene Methode einzufügen!
Arafangion
2
Der Fragesteller ist neugierig, wie detailliert seine Methodenaufrufe sein sollen. Es ist nicht erforderlich, eine Methode zum Inkrementieren einer Ganzzahl zu erstellen, wenn Sie nur verwenden könneni++;
Peaches491
Tatsächlich ist es sehr wertvoll, eine Methode zu erstellen, auch wenn keine Chance besteht, dass sie jemals wiederverwendet wird. Nur einem Codeblock einen Namen zu geben und die Gesamtstruktur erscheinen zu lassen, ist an sich schon ein großer Vorteil.
Joffrey
1

Behalten Sie die clear()Methode bei, wenn sie die Lesbarkeit verbessert. Nicht wartbarer Code ist teurer.

Fabian Barney
quelle
1

Durch die Optimierung von Compilern wird die Redundanz aus diesen "zusätzlichen" Vorgängen normalerweise recht gut entfernt. In vielen Fällen besteht kein Unterschied zwischen "optimiertem" Code und Code, der einfach so geschrieben wurde, wie Sie es möchten, und der durch einen optimierenden Compiler ausgeführt wird. Das heißt, der optimierende Compiler leistet normalerweise genauso gute Arbeit wie Sie, und dies ohne Verschlechterung des Quellcodes. Tatsächlich ist "handoptimierter" Code oft WENIGER effizient, da der Compiler bei der Optimierung viele Dinge berücksichtigt. Lassen Sie Ihren Code in einem lesbaren Format und sorgen Sie sich erst zu einem späteren Zeitpunkt um die Optimierung.

"Vorzeitige Optimierung ist die Wurzel allen Übels." - Donald Knuth

Paul Sonier
quelle
0

Ich würde mich nicht so sehr um den Methodenaufruf kümmern, sondern um die Logik der Methode. Wenn es sich um kritische Systeme handelte und das System dann "schnell" sein musste, würde ich mich mit der Optimierung von Codes befassen, deren Ausführung lange dauert.

Buhake Sindi
quelle
0

Angesichts des Speichers moderner Computer ist dies sehr kostengünstig. Es ist immer besser, Ihren Code in Methoden aufzuteilen, damit jemand schnell lesen kann, was los ist. Dies hilft auch beim Eingrenzen von Fehlern im Code, wenn der Fehler auf eine einzelne Methode mit wenigen Zeilen beschränkt ist.

Adamjmarkham
quelle
0

Wie andere gesagt haben, sind die Kosten für den Methodenaufruf trivial, da der Compiler ihn für Sie optimieren wird.

Es besteht jedoch die Gefahr, dass Methodenaufrufe von einem Konstruktor an Instanzmethoden ausgeführt werden. Sie laufen Gefahr, die Instanzmethode später zu aktualisieren, damit sie möglicherweise versucht, eine Instanzvariable zu verwenden, die vom Konstruktor noch nicht initiiert wurde. Das heißt, Sie möchten die Bautätigkeiten nicht unbedingt vom Konstruktor trennen.

Eine andere Frage - Ihre clear () -Methode setzt den Stamm auf LEER, der beim Erstellen des Objekts initialisiert wird. Wenn Sie dann Knoten zu LEER hinzufügen und dann clear () aufrufen, wird der Stammknoten nicht zurückgesetzt. Ist das das Verhalten, das Sie wollen?

Matthew Flynn
quelle