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?
java
performance
methods
call
jhlu87
quelle
quelle
Antworten:
Der Compiler kann diese Optimierung durchführen. Und die JVM auch. Die vom Compiler-Writer und JVM-Autoren verwendete Terminologie lautet "Inline-Erweiterung".
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.
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.
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.
quelle
System.nanoTime()
oderSystem.currentTimeMillis
ist 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.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
,static
oderprivate
) 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
BinarySearchTree
aufgerufen hinzufügtLoggingBinarySearchTree
, die alle öffentlichen Methoden mit Code überschreibt, wie:public void clear(){ this.callLog.addCall("clear"); super.clear(); }
Dann
LoggingBinarySearchTree
wird das niemals konstruierbar sein! Das Problem ist, dass diesthis.callLog
der Fall ist,null
wenn derBinarySearchTree
Konstruktor ausgeführt wird, aber dasclear
aufgerufene ist das überschriebene, und Sie erhalten einNullPointerException
.Beachten Sie, dass sich Java und C ++ hier unterscheiden: In C ++ ruft ein Konstruktor
virtual
der 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
clear
Methode beim Aufruf vom Konstruktor zu integrieren , aber im Allgemeinen sollten Sie in Java alle gewünschten Methodenaufrufe ausführen.quelle
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.quelle
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.
quelle
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.
quelle
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.
quelle
Das Muster, dem ich folge, ist, ob diese fragliche Methode eine der folgenden Bedingungen erfüllen würde oder nicht:
Wenn eine der oben genannten Aussagen zutrifft, sollte sie in eine eigene Methode eingepackt werden.
quelle
i++;
Behalten Sie die
clear()
Methode bei, wenn sie die Lesbarkeit verbessert. Nicht wartbarer Code ist teurer.quelle
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.
quelle
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.
quelle
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.
quelle
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?
quelle