2010-12-03 9 views
7

リリース前にすべてのログ出力を取り除く必要がないように、デバッグ出力を処理するクラスを作成しました。Android:空のメソッドを実行すると、どのくらいのオーバーヘッドが生成されますか?

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

別の質問を読んだ後、私は一定のRelease.DEBUGがfalseの場合、if文の内容がコンパイルされていないことを学びました。

この空のメソッドを実行すると、どのくらいのオーバーヘッドが生成されるのですか。 (if節が削除されると、メソッドにコードが残っていません)アプリケーションに何らかの影響がありますか?携帯電話のために書いたときに明らかにパフォーマンスがP =大きな問題である

おかげ

ゲイリー

答えて

14

測定:

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 

コントロール:私は測定を数回繰り返してきた

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 

。有意な偏差は見られなかった。 あなたは 、コールごとのコストは(おそらくJITのコンパイルに)ワークロードに応じて大きく異なりますことを見ることができますが、3つの結論を引き出すことができます。

  1. のDalvik/javaはデッドコードの最適化を吸う

  2. 静的な関数呼び出しは、非静的 (非静的関数は仮想であり、仮想テーブル内でルックアップする必要があります)

  3. がネクサスS上のコストがで70nsよりも大きくない/コールよりもはるかに優れて最適化することができ、 (それは〜70 cpuサイクル)回の空のループ反復(すなわち、ループの繰り返し)のコストに匹敵する。 1つの増分とローカル変数の1つの条件チェック)

あなたのケースでは、文字列引数が常に評価されることに注意してください。文字列連結を行う場合は、中間文字列を作成する必要があります。これは非常にコストがかかり、多くのgcを必要とします。例えば、機能を実行:100回のこのような呼び出しの

例えば

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

10^4として引数で呼び出さ

void empty(String string){ 
} 

反復が10秒を要します。これは10us /コール、すなわち空のコールよりも1000倍遅い。それはまた、GC活動の巨大な量を生成します。これを回避する唯一の方法は、手動で関数をインライン化することです。つまり、デバッグ関数の代わりに>> if < <ステートメントを使用します。それは醜いですが、それを機能させる唯一の方法です。

+0

ええコメントを外してしまえば、それは本当にあなたを噛む文字列ビルダです。その理由は、ランタイムjitまたはaotコンパイラは、文字列の作成が失敗してプログラムのフローに影響を与える場合、事前に決定できないためです。 周囲が乱雑です。プリコンパイラは正しい方法ですが、今はアンドロイドのデフォルトツールで簡単に行うことはできません。 –

2

あなたが深くネストされたループ内からこれを呼び出す場合を除き、私はそれについて心配しないでしょう。

2

良いコンパイラは空のメソッド全体を削除し、オーバーヘッドがまったく発生しません。私はDalvikコンパイラが既にこれを行っているかどうかはわかりませんが、少なくともFroyoのJust-In-Timeコンパイラが到着して以来、おそらくそれは疑わしいです。

も参照してください:パフォーマンスの面でInline expansion

+0

apktoolのコピーを入手し、アプリケーションを逆コンパイルし、dalvikバイトコードから最適化されているかどうかを確認します。後でインストールやランタイムのロード時に最適化できる段階がありますが、それが最も明白なようです。 –

+0

apkを逆コンパイルしてもアートやdalvikの最適化が実際には見えない場合は、javaコンパイラとdexコンバータ(場合によってはproguard)によって行われた最適化のみが表示されます。キャッシュされたものをさらに内側から抽出する必要があります。 –

2

は、デバッグ関数に渡されるメッセージの生成のオーバーヘッドが、彼らはメモリの割り当ては、おそらく、たとえばんそのため、多くの深刻なことを行っている

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

メッセージがビンに入れられてもまだ発生します。 残念ながら、あなたの目標がパフォーマンスであれば、関数自体の内部ではなく、この関数の呼び出しの周りに本当に "if(Release.DEBUG)"が必要です。これは多くのアンドロイドコードで表示されます。アンドロイド2.3.2でネクサスS上で行わ

1

これは興味深い質問で、@misiu_mp分析が好きなので、Android 6.0.1を実行しているNexus 7の2016テストでアップデートすると思いました。

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() { } 

sleep()Log.dバッファをオーバーフロー防止するために添加した:ここで、テストコードです。

私はそれを周りに何回も演奏し、その結果を@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 

静的メソッドの呼び出しは常に非静的メソッドの呼び出しよりも少し速かったが、それはように思われます)は、Android 2.3.2以降、格差が大幅に解消されています。また、静的かどうかにかかわらず、空のメソッドを呼び出すためのコストはまだあります。

しかし、時間のヒストグラムを見ると、面白いことが分かります。大部分の呼び出しは、静的であるかどうかにかかわらず、30〜40nsを要し、データを正確に見ると、それらは事実上すべて30nsです。 (メソッド呼び出しをコメントアウト)空のループで同じコードを実行

enter image description here

余りが正確30nsのであるがしかし、測定時間の約3/4が0nsであり、8nsの平均速度を生成します。

このデータをどのように説明するのかはわかりませんが、@ misiu_mpの結論はまだ成立していません。空の静的メソッドと非静的メソッドの違いはごくわずかであり、測定の優位性は正確に30nsです。つまり、空のメソッドを実行するにはまだゼロ以外のコストがあるように見えます。

+0

一般的なデバッグメッセージのように、呼び出しメソッドに文字列の作成を追加すると "status" + int、私は何も(静的メソッド内の文字列で行われていないにもかかわらず)何の違いもないと思います。 –

関連する問題