Performance Methodenaufruf - if Abfrage

Status
Nicht offen für weitere Antworten.

MQue

Top Contributor
Hab gerade versucht herauszufinden, was performanter ist, vor dem Methodenaufruf eine Abfrage zu machen oder die Methode einfach aufzurufen und in der Methode zu validieren.
Zweitere Variante scheint ca. um das Doppelte schneller zu sein als die erstere Variante.
Kann man das irgendwie interpretieren?
Beste Grüße,


Java:
package commandseparator;

public class Main {

    public Main() {
        String test = null;
        int erg = 2;
        long start1 = System.currentTimeMillis();
        System.out.println("");
        for (int i = 0; i < 1000000000; i++) {
            erg *= 2;
            testMethode(test);
            }
        long end1 = System.currentTimeMillis();

        int erg2 = 2;
        long start2 = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            erg2 *= 2;
            if(test != null)
                testMethode(test);
            }
        long end2 = System.currentTimeMillis();
        System.out.println("Ergebnis: " + (end1-start1) + "; " + (end2-start2));
        }

    private void testMethode(String test) {
        if(test == null)
            return;
        }

    public static void main(String[] args) {
        new Main();
        }
}
 
Ergebnis: 0; 0
😀

Interessanterweise wollte ich kürzlich mal einen Thread zu genau diesem Thema aufmachen. Dabei wäre es aber nicht um die Frage der Performance gegangen, sondern eher um die "philosophische" Frage, was denn "schöner" oder "besser" ist.
Es gibt da unterschiedliche Pros und Contras - vermutlich einer der vielen Punkte, wo man von Fall zu Fall abwägen muss...
 
Aber noch so als Nachtrag: Es KANN schon Fälle geben, wo Microbenchmarks Sinn machen. Allerdings immer mit einem gewissen Vorbehalt, und nur, wenn man sie sinnvoll modellieren kann. In diesem Beispiel ist das praktisch nicht möglich.
 
Bei solchen kleinen Stücken weiß man auch nie genau, was der Compiler jetzt wie optimiert. Und selbst wenn man ein halbwegs aussagekräftiges Ergebnis hat, kann es sein, dass das in Produktivumgebungen (schließlich ist das hier nur ein sinnfreies Trivialbeispiel) aufgrund geänderter Parameter wieder völlig anders aussieht.

Dabei wäre es aber nicht um die Frage der Performance gegangen, sondern eher um die "philosophische" Frage, was denn "schöner" oder "besser" ist.

Die Frage ist da sicher interessanter. Prinzipiell ist das nur Definitionssache. Es gibt genug Methoden, die kein [c]null[/c] als Parameter akzeptieren und eine NPE (oder vielleicht auch IllegalArgumentException) werfen. Andererseits kann es auch sinnvoll sein, [c]null[/c] zu übergeben und erst später zu prüfen. Das hängt alles vom genauen Einsatz ab. Solange man dann aber konsequent bleibt und richtig dokumentiert, ist ja schonmal viel gewonnen.
 
Ja (auch auf die Gefahr hin, dass das jetzt als Thread-Hijacking angesehen wird) es ging da quasi um die "Kompetenzen" - WER entscheidet, ob in einer Methode etwas gemacht wird (oder ob eine Methode aufgerufen wird). Die Grenzen sind da fließend...
Java:
if (node != null) process(node);
// oder
if (node != null && node.getContent() != null) process(node);
// oder
if (node != null && node.getContent() != null && node.getContent().isValid()) process(node);

// Und dann einfach
void process(Node node)
{
    processValidNodeContent(node.getContent());
}
oder
Java:
process(node);

// Und dann 
void process(Node node)
{
    if (node != null && node.getContent() != null && node.getContent().isValid()) 
    {
        processValidNodeContent(node.getContent());
    }
}

Das (selbstverständlich) wichtigste ist, dass das Verhalten dokumentiert ist:
Java:
/** Processes the content of the node. The node and its contents must be
 *   non-null, and the content must be valid, otherwise the world will explode...
 *   ...
 */
void process(Node node)
vs.
Java:
/** Processes the content of the node if the node and its contents are 
 *   non-null, and the content is valid, otherwise does nothing...
 *   ...
 */
void process(Node node)

Eigentlich hängt die Entscheidung auch von der gewünschten Art der Anwendung ab - sofern man die vorher schon kennt. Natürlich wäre es schöner, wenn man nicht
Java:
if (node0 != null && node0.getContent() != null && node0.getContent().isValid()) process(node0);
if (node1 != null && node1.getContent() != null && node1.getContent().isValid()) process(node1);
if (node2 != null && node2.getContent() != null && node2.getContent().isValid()) process(node2);
machen müßte, sondern einfach
Java:
process(node0);
process(node1);
process(node2);
zumal man in der "process"-Methode eigentlich sowieso die Eingaben auf Gültigkeit prüfen sollte, aber es gibt eben Fälle, wo das dann wieder unschön wäre, und durch den "Vertrag" (die Doku, die besagt, wie die Methode aufgerufen werden darf) eigentlich schon abgedeckt sein sollte...
 
Interessant wäre auch mal, was der Compiler aus deinem Beispiel macht (javap.exe), wird wohl aber eins zu eins umgesetzt und nicht weiter optimiert. Ich denke das der Test vor dem Methodenaufruf tatsächlich schneller ist, als im Methodenaufruf, da hier einiges an Overhead seitens der VM wegfallen wird. Wenn die VM eine Methode aufruft, werden verschiedene Dinge erledigt: Methoden-Stackframe erstellen, Variablen kopieren etc.
 
ich denke das der Unterschied ob während des Testens die Maus bewegt wird oder nicht stärker ausfällt als der methodenaufruf^^
 
Aber man kann wohl generell sagen, dass es teurer ist, eine Methode aufzurufen (wie fastjack schon erwähnte wegen Stacks und Variablen etc.), als sie nicht aufzurufen 😉
Ob es natürlich deswegen sinnvoll ist, alle Prüfungen auszulagern, würde ich nicht sagen. Hier sollte man sich eher an saubere Implementierung und Kontext halten als an die paar Millisekunden zu denken, die man sich theoretisch sparen könnte.
 
Wie der müde Joe schon gezeigt hat, kommt es einfach auch darauf an, wie oft die Methode aufgerufen wird. Irgendwann kommt der HotSpotCompiler auf den Trichter, das z.B. durch Inlining zu optimieren, und schon sieht die Sache anders aus, vermutlich besser ;-)
 
Nachtrag: es ist immer gut sich bei solchen Fragen mal den Binärcode der .class Datei anzusehen (nicht den decompilierten Javacode). Da sieht man dann tatsächlich wie was vom normalen Compiler schon optimiert wird.
 
Hab mal die Klasse compiliert und dann mit jad wieder decompiliert, und da kam das dabei raus,
Eingentlich nichts aufregendes ausser das in der for-Schleife der iterier- Wert in hex dargestellt wird und das die Code converntion nicht so ganz passt (eigene Zeile für {).

Java:
package commandseparator;

import java.io.PrintStream;

public class Main
{

    public Main()
    {
        String test = null;
        int erg = 2;
        long start1 = System.currentTimeMillis();
        System.out.println("");
        for(int i = 0; i < 0x3b9aca00; i++)
        {
            erg *= 2;
            testMethode(test);
        }

        long end1 = System.currentTimeMillis();
        int erg2 = 2;
        long start2 = System.currentTimeMillis();
        for(int i = 0; i < 0x3b9aca00; i++)
        {
            erg2 *= 2;
            if(test != null)
                testMethode(test);
        }

        long end2 = System.currentTimeMillis();
        System.out.println((new StringBuilder()).append("Ergebnis: ").append(end1 - start1).append("; ").append(end2 - start2).toString());
    }

    private void testMethode(String test)
    {
        if(test == null)
            return;
        else
            return;
    }

    public static void main(String args[])
    {
        new Main();
    }
}
 
>>[ (nicht den decompilierten javacode)

Habs schon gelesen, wollte aber trotzdem mal schaun was der Decompiler daraus macht.
Beste Grüße,

Das wäre mal mein Bytecode, von dem ich nur Bruchteile verstehe (soweit es C- ähnlich ist wi goto usw.)
Code:
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   aconst_null
   5:   astore_1
   6:   iconst_2
   7:   istore_2
   8:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   11:  lstore_3
   12:  getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
   15:  ldc     #4; //String
   17:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   20:  iconst_0
   21:  istore  5
   23:  iload   5
   25:  ldc     #6; //int 1000000000
   27:  if_icmpge       45
   30:  iload_2
   31:  iconst_2
   32:  imul
   33:  istore_2
   34:  aload_0
   35:  aload_1
   36:  invokespecial   #7; //Method testMethode:(Ljava/lang/String;)V
   39:  iinc    5, 1
   42:  goto    23
   45:  invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   48:  lstore  5
   50:  iconst_2
   51:  istore  7
   53:  invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   56:  lstore  8
   58:  iconst_0
   59:  istore  10
   61:  iload   10
   63:  ldc     #6; //int 1000000000
   65:  if_icmpge       89
   68:  iload   7
   70:  iconst_2
   71:  imul
   72:  istore  7
   74:  aload_1
   75:  ifnull  83
   78:  aload_0
   79:  aload_1
   80:  invokespecial   #7; //Method testMethode:(Ljava/lang/String;)V
   83:  iinc    10, 1
   86:  goto    61
   89:  invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   92:  lstore  10
   94:  getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
   97:  new     #8; //class java/lang/StringBuilder
   100: dup
   101: invokespecial   #9; //Method java/lang/StringBuilder."<init>":()V
   104: ldc     #10; //String Ergebnis:
   106: invokevirtual   #11; //Method java/lang/StringBuilder.append:(Ljava/lang
/String;)Ljava/lang/StringBuilder;
   109: lload   5
   111: lload_3
   112: lsub
   113: invokevirtual   #12; //Method java/lang/StringBuilder.append:(J)Ljava/la
ng/StringBuilder;
   116: ldc     #13; //String ;
   118: invokevirtual   #11; //Method java/lang/StringBuilder.append:(Ljava/lang
/String;)Ljava/lang/StringBuilder;
   121: lload   10
   123: lload   8
   125: lsub
   126: invokevirtual   #12; //Method java/lang/StringBuilder.append:(J)Ljava/la
ng/StringBuilder;
   129: invokevirtual   #14; //Method java/lang/StringBuilder.toString:()Ljava/l
ang/String;
   132: invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   135: return

public static void main(java.lang.String[]);
  Code:
   0:   new     #15; //class commandseparator/Main
   3:   dup
   4:   invokespecial   #16; //Method "<init>":()V
   7:   pop
   8:   return

}
 
Zuletzt bearbeitet:
Daran erkennt man auch eigentlich nur, das der null-Check durchgeführt wird bevor die Methode aufgerufen wird, bei Zeile 75. Bei ersterer Variante wird die Methode gleich aufgerufen. Also analog zur eigentlichen Programmierung und den vorherigen Erkenntnissen.
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben