Happy My Life

日常とか技術とか

Android NDK利用時のオーバーヘッドは?

いつも言われるのが「NDKを呼び出すときはオーバーヘッドがあるから頻繁に呼び出さない方がいいよ」という話。

それじゃどれだけのオーバーヘッドがあるのよ? という事で実際に計測してみた。

今回計測したのは、JNI呼び出しとして

  • int addInt(int i, int j); // 引数 int x 2 返値 int
  • int getInt(); // 返値 int

を10000000回呼び出すパターン。あまり凝った事はしてない。

以下、内容と報告を。

計測環境

HTC Desire + Froyo(2.2) + Android SDK 2.2

ソースコード

こんな感じで作成してみた。あくまで簡易ベンチマークとしての役目しかない。 つうか、ベンチマークってこんな作りでいいのかな? 教えてエラい人。コンパイラの最適化にあまり影響されないようにはしたつもりなのだが。

package com.example.android.ndkbench;

import android.app.Activity;
import android.graphics.Point;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends Activity {
    private TextView textView;
    private String TAG = this.getClass().getName();

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    
        textView = (TextView)findViewById(R.id.textview);
    
        int cnt = 0;
        long loopcnt = 10000000;
        
        // javaで足し算
        long sum = 0;
        long start = System.currentTimeMillis();
        for (int i = 0; i < loopcnt; i++){
            sum += 1;
        }
        long end = System.currentTimeMillis();
        double diff = (end - start);
        double sec = diff/1000;
        String text = "java time:"+ sec + " sum:"+sum+"n";
        textView.setText(text);

        
        // ダミーの呼出
        sum = 0;
        for (int i = 0; i < loopcnt; i++){
            sum += getInt();
        }
        text += "dummySUM:"+sum + "n";
        
        // int getInt()
        sum=0;
        start = System.currentTimeMillis();
        for (int i = 0; i < loopcnt; i++){
            sum += getInt();
        }
        end = System.currentTimeMillis();
        diff = (end - start);
        sec = diff/1000;
        text += "getInt() time:"+ sec + " sum:"+sum + "n";

        // int addInt()
        sum=0;
        start = System.currentTimeMillis();
        for (int i = 0; i < loopcnt; i++){
            sum += addInt(1,1);
        }
        end = System.currentTimeMillis();
        diff = (end - start);
        sec = diff/1000;
        text += "addInt() time:"+ sec + " sum:"+sum + "n";

        textView.setText(text);
        
    }
    
    public native int getInt();
    public native int addInt(int i,int j);

    static {
        System.loadLibrary("bench-jni");
    }
}

Cではこんな感じ。

#include 
#include 

jint
Java_com_example_android_ndkbench_MainActivity_getInt( JNIEnv* env,
                                                  jobject thiz )
{
    return 1;
}

jint
Java_com_example_android_ndkbench_MainActivity_addInt( JNIEnv* env,
                                                       jobject thiz,jint i, jint j)
{
    return i+j;
}

計測結果

計測結果はこんな感じ。これは実行1回目。JITは影響してないはず。

device_22_22s

個人的には思っていたよりオーバーヘッドが無いんじゃないかと。これなら頻繁に呼び出しても、大丈夫そう。

ちなみに、同一ファイルの実行2回目。

device_22_22_3s

なにこれ、JIT早っ。って事で、JITがかなり強力なのがよく分かった(笑)

疑問

コード中で、本格的にgetInt()をグルグルとループを回す前に、ダミーの呼出ってのをやっている。これについて少し解説すると。

これをやっている理由は、ダミーを挟むのと挟まないので実行結果が変化する。 ちなみにダミーを挟まない実行結果1回目はこの通り。

device_22_22_nodummy_s

getInt()にかかる時間がやたらと遅くなっている。getInt()とaddInt()を入れ替えても、はやり先に呼ばれた方が遅くなる。

あと、ダミーで呼ぶ回数を1000回程度のループにしても、結果が変化しない。soファイルをロードするのに時間がかかっているのかと考えたのだが、そうでも無さそうだ。うーむ。

で、いろいろ考えてみたが、なんで、ここまで計測時間が変化する原因が見当つかない。分かる人は教えて欲しいです。ほんとに…。

結論

  • NDKのオーバーヘッドは意外と少ない
  • JIT早っ
  • NDKでのJIT対象外っぽい