2010-12-03 3 views
7

Ich habe eine Klasse erstellt, um meine Debug-Ausgaben zu behandeln, so dass ich nicht alle meine Log-Ausgaben vor der Veröffentlichung entfernen muss.Android: Wie viel Overhead wird generiert, indem eine leere Methode ausgeführt wird?

public class Debug { 
    public static void debug(String module, String message) { 
     if(Release.DEBUG) 
      Log.d(module, message); 
    } 
} 

Nach einer anderen Frage zu lesen, habe ich gelernt, dass der Inhalt der if-Anweisung kompiliert werden, nicht wenn die Konstante Release.DEBUG falsch ist.

Was ich wissen möchte ist, wie viel Overhead generiert wird, indem diese leere Methode ausgeführt wird? (Sobald die if-Klausel entfernt wurde, ist in der Methode kein Code mehr vorhanden.) Wird es Auswirkungen auf meine Anwendung haben? Offensichtlich Leistung ist ein großes Problem, wenn für Mobiltelefone = P

Dank

Gary

Antwort

14

Messungen an Nexus S mit Android 2.3.2 getan:

10^6 iterations of 1000 calls to an empty static void function: 21s <==> 21ns/call 
10^6 iterations of 1000 calls to an empty non-static void function: 65s <==> 65ns/call 

10^6 iterations of 500 calls to an empty static void function: 3.5s <==> 7ns/call 
10^6 iterations of 500 calls to an empty non-static void function: 28s <==> 56ns/call 

10^6 iterations of 100 calls to an empty static void function: 2.4s <==> 24ns/call 
10^6 iterations of 100 calls to an empty non-static void function: 2.9s <==> 29ns/call 

Kontrolle:

10^6 iterations of an empty loop: 41ms <==> 41ns/iteration 
10^7 iterations of an empty loop: 560ms <==> 56ns/iteration 
10^9 iterations of an empty loop: 9300ms <==> 9.3ns/iteration 

ich die Messungen mehrmals wiederholt haben. Es wurden keine signifikanten Abweichungen gefunden. Sie können sehen, dass die pro-Call-Kosten stark je nach Arbeitsbelastung variieren können (möglicherweise aufgrund JIT Compilierung), aber 3 Schlussfolgerungen gezogen werden können:

  1. Dalvik/java saugt bei der Optimierung von totem Code

  2. statische Funktion Anrufe können viel besser als nicht-statische (nicht-statische Funktionen sind virtuelle und müssen nachgeschlagen werden in einer virtuellen Tabelle)

  3. die Kosten auf nexus s nicht größer als 70 ns/Anruf optimiert werden (das sind ~ 70 CPU-Zyklen) und ist vergleichbar mit den Kosten einer leeren für-Schleife-Iteration (d. H. einen Schritt und eine Zustandsüberprüfung auf eine lokale Variable)

Beachten Sie, dass die String-Argument in Ihrem Fall wird immer ausgewertet werden.Wenn Sie eine Kettenverkettung durchführen, müssen Sie Zwischenstrings erstellen. Dies wird sehr kostspielig sein und eine Menge von GC beinhalten. Zum Beispiel die Ausführung einer Funktion:

void empty(String string){ 
} 

mit Argumenten aufgerufen wie

empty("Hello " + 42 + " this is a string " + count); 

10^4 Iterationen von 100 solcher Anrufe 10s dauert. Das ist 10 us/Anruf, d. H. ~ 1000 mal langsamer als nur ein leerer Anruf. Es produziert auch eine große Menge an GC-Aktivität. Die einzige Möglichkeit, dies zu vermeiden, besteht darin, die Funktion manuell zu inline zu schreiben, d.h. die Anweisung >> if < < anstelle des Debug-Funktionsaufrufs zu verwenden. Es ist hässlich, aber der einzige Weg, es zum Laufen zu bringen.

+0

yeah Es ist wirklich der Stringbuilder, der dich beißt, wenn du unkommentiert bist. Der Grund dafür ist, dass der Runtime-JIT- oder AOT-Compiler nicht im Voraus bestimmen kann, ob die String-Erstellung fehlschlägt und sich auf den Programmablauf auswirkt. ein wenn es herum chaotisch ist. Ein Pre-Compiler wäre der richtige Weg, aber momentan gibt es keinen einfachen Weg, dies mit den Androiden-Standardwerkzeugen zu tun, denke ich. –

2

Schreiben Es sei denn, Sie nennen dies aus einer tief verschachtelten Schleife, würde ich nicht darum kümmern.

2

Ein guter Compiler entfernt die gesamte leere Methode, sodass überhaupt kein Overhead entsteht. Ich bin mir nicht sicher, ob der Dalvik-Compiler das bereits tut, aber ich vermute, dass es wahrscheinlich ist, zumindest seit der Ankunft des Just-in-Time-Compilers mit Froyo.

Siehe auch: Inline expansion

+0

Holen Sie sich eine Kopie von apktool, dekompilieren Sie Ihre App und sehen Sie, ob sie aus dem Dalvik-Bytecode optimiert wurde. Es gibt spätere Phasen, in denen es während der Installation oder beim Laden der Laufzeit optimiert werden könnte, aber das scheint am offensichtlichsten zu sein. –

+0

Wenn Sie die Apk dekompilieren, sehen Sie die Kunst- oder Dalvik-Optimierungen nicht wirklich, Sie sehen nur die Optimierungen, die vom Java-Compiler und dex-Konverter (und möglicherweise proguard) durchgeführt werden. Sie müssten das zwischengespeicherte Zeug aus dem Inneren herausholen. –

2

In Bezug auf der Leistung der Aufwand, die Nachrichten zu erzeugen, die in die Debug-Funktion übergeben bekommen wird viel ernster sein, da seine wahrscheinlichen sie Speicherzuordnungen zB

Debug.debug(mymodule, "My error message" + myerrorcode); 

Welche noch auftreten wird, selbst wenn die Nachricht bündig ist. Leider brauchen Sie wirklich die "if (Release.DEBUG)" um die Aufrufe dieser Funktion und nicht innerhalb der Funktion selbst, wenn Ihr Ziel die Leistung ist, und Sie werden dies in einer Menge Android-Code sehen.

1

Dies ist eine interessante Frage und ich mag @misiu_mp Analyse, also dachte ich, ich würde es mit einem Test 2016 auf einem Nexus 7 mit Android 6.0.1 aktualisieren. Hier ist der Testcode:

public void runSpeedTest() { 
    long startTime; 
    long[] times = new long[100000]; 
    long[] staticTimes = new long[100000]; 
    for (int i = 0; i < times.length; i++) { 
     startTime = System.nanoTime(); 
     for (int j = 0; j < 1000; j++) { 
      emptyMethod(); 
     } 
     times[i] = (System.nanoTime() - startTime)/1000; 
     startTime = System.nanoTime(); 
     for (int j = 0; j < 1000; j++) { 
      emptyStaticMethod(); 
     } 
     staticTimes[i] = (System.nanoTime() - startTime)/1000; 
    } 
    int timesSum = 0; 
    for (int i = 0; i < times.length; i++) { timesSum += times[i]; Log.d("status", "time," + times[i]); sleep(); } 
    int timesStaticSum = 0; 
    for (int i = 0; i < times.length; i++) { timesStaticSum += staticTimes[i]; Log.d("status", "statictime," + staticTimes[i]); sleep(); } 
    sleep(); 
    Log.d("status", "final speed = " + (timesSum/times.length)); 
    Log.d("status", "final static speed = " + (timesStaticSum/times.length)); 
} 

private void sleep() { 
    try { 
     Thread.sleep(10); 
    } catch (InterruptedException e) { 
     // TODO Auto-generated catch block 
     e.printStackTrace(); 
    } 
} 

private void emptyMethod() { } 
private static void emptyStaticMethod() { } 

Die sleep() wurde hinzugefügt, um den Log.d Pufferüberlauf zu verhindern.

spielte ich mit ihm um viele Male und die Ergebnisse waren ziemlich konsistent mit @misiu_mp:

10^5 iterations of 1000 calls to an empty static void function: 29ns/call 
10^5 iterations of 1000 calls to an empty non-static void function: 34ns/call 

Die statischen Aufruf Methode war immer etwas schneller als die nicht-statische Methode aufrufen, aber es wäre, dass ein erscheinen) Die Lücke hat sich seit Android 2.3.2 deutlich geschlossen und b) es kostet immer noch Aufrufe an eine leere Methode, statisch oder nicht.

Ein Blick auf ein Histogramm von Zeiten offenbart jedoch etwas Interessantes. Die Mehrheit des Anrufs, egal ob statisch oder nicht, benötigt zwischen 30 und 40 ns und schaut genau auf die Daten, die sie praktisch alle 30 ns genau sind.

enter image description here

den gleichen Code mit leeren Schleifen Laufen (Kommentierung der Verfahrensaufrufe) erzeugt eine Durchschnittsgeschwindigkeit von 8 ns, jedoch etwa 3/4 der gemessenen Zeiten 0ns sind, während der Rest ist genau 30ns.

Ich bin mir nicht sicher, wie ich diese Daten erfassen soll, aber ich bin mir nicht sicher, ob die Schlussfolgerungen von @ misiu_mp immer noch gelten. Der Unterschied zwischen leeren statischen und nicht-statischen Methoden ist vernachlässigbar und das Überwiegen von Messungen ist genau 30ns. Davon abgesehen scheint es, dass es noch einige Kosten gibt, die nicht null sind, um leere Methoden auszuführen.

+0

Wenn Sie der aufrufenden Methode eine String-Erstellung hinzufügen, wäre eine typische Debug-Nachricht "status" + int. Ich denke, Sie würden überhaupt keinen Unterschied sehen (obwohl nichts mit der Zeichenfolge in der statischen Methode getan wird). –

Verwandte Themen