JNI学习笔记:缓存技术



1 前言

  为什么使用缓存技术?使用JNI接口访问字段或方法时,需要通过字段或方法的ID来进行操作,获取ID的过程是一个检索的过程,该过程相对而言比较耗时,缓存技术就是为了减少该过程的时间消耗。缓存技术又根据发生时刻不同分为两种:使用时缓存Caching at the Point of use)和类初始化缓存Caching in the Defining Class’s Initializer)。
  本文通过两个演示程序分别介绍这两种缓存技术的使用方法,并在最后通过一个对比程序验证缓存所带来的速度提升。

2 使用时缓存

  字段和方法的ID可以在访问或者被回调的时候缓存起来,在Native层使用JNI定义的数据类型调用Java的相关字段或者方法时,可以将存储ID的数据类型设置为静态的,这样本地方法重复调用时,不必重新搜索字段ID

  • Java代码
public class JNICacheIni{
    static{
        System.loadLibrary("JNITest");
    }
    public native void cacheMethod();
    private String str = "I am a String from Java!";

    public static void main(String args[]){
        JNICacheIni jnic = new JNICacheIni();
        jnic.cacheMethod();
        System.out.println("After calling JNI function:");
        System.out.println(jnic.str);
    }
}
  • Native 代码
JNIEXPORT void JNICALL Java_JNICache_cacheMethod
(JNIEnv *env, jobject obj){
    jclass jcla = env->GetObjectClass(obj);
    jboolean jb = JNI_TRUE;
    // 使用id时,设置为static
    static jfieldID jfid = env->GetFieldID(jcla, "str", "Ljava/lang/String;");

    jstring jstr = (jstring)env->GetObjectField(obj, jfid);
    const char*str = env->GetStringUTFChars(jstr, &jb);
    cout << "In C++:" << endl;
    cout << str << endl;
    const char *new_str = "I am a string from C++.";
    jstring new_jstr = env->NewStringUTF(new_str);
    env->SetObjectField(obj, jfid, new_jstr);
}

3 类初始化缓存

  VM在调用一个类的方法和字段之前,都会执行类的静态初始化过程,在静态初始化的过程中缓存要访问的字段或方法ID是一个不错的选择。
  我们可以在类初始化的过程中增加一个Native函数,该函数获取目标ID并保存为全局变量, 这样在Java层对象访问JNI对应的ID时,不必再重新搜索。

  • Java代码
public class JNICacheIni{
    static{
        System.loadLibrary("JNITest");
        initIDs();
    }
    public native static void initIDs();
    public native void nativeMethod();
    private void callBack(){
        System.out.println("In Java");
    }

    public static void main(String args[]){
        JNICacheIni jnic = new JNICacheIni();
        jnic.nativeMethod();
    }
}
  • Native代码
jmethodID JNICacheIni_initIDs_jmid;
JNIEXPORT void JNICALL Java_JNICacheIni_initIDs
(JNIEnv *env, jclass jcl){
    JNICacheIni_initIDs_jmid = env->GetMethodID(jcl, "callback", "()V");
}

JNIEXPORT void JNICALL Java_JNICacheIni_nativeMethod
(JNIEnv *env, jobject obj){
    cout << "In C++:" << endl;
    env->CallVoidMethod(obj, JNICacheIni_initIDs_jmid);
}

4 对比程序

  下面通过一个对比程序来验证缓存带来的速度提升,通过成员函数、使用缓存的JNI函数和不使用缓存的JNI函数,分别访问一个成员变量1024*1024次,并在函数内完成累加,修改成员变量的值,完成一次交互,最后统计时间。

  • Java代码
public class JNICacheTest{
    static {
        System.loadLibrary("JNITest");
        initsID();
    }

    private native void addCount();
    private native void addCountByCache();
    private native void addCountByInits();
    private native static void initsID();

    private int count;
    private final int MAX_TIMES = 1024*1024;

    public void add(){
        count++;
    }

    public void setCount(int count){
        this.count = count;
    }

    public static void main(String args[]){
        JNICacheTest jnict = new JNICacheTest();
        long startTime = System.currentTimeMillis();
        for(int i=0;i<jnict.MAX_TIMES;i++){
            jnict.add();
        }
        long time = (System.currentTimeMillis() - startTime);
        System.out.println("Member function:"+time+"ms");

        jnict.setCount(0);
        startTime = System.currentTimeMillis();
        for(int i =0;i<jnict.MAX_TIMES;i++){
            jnict.addCount();
        }
        time = (System.currentTimeMillis() - startTime);
        System.out.println("JNI function:"+time+"ms");

        jnict.setCount(0);
        startTime = System.currentTimeMillis();
        for(int i =0;i<jnict.MAX_TIMES;i++){
            jnict.addCountByCache();
        }
        time = (System.currentTimeMillis() - startTime);
        System.out.println("JNI cache function:"+time+"ms");
        jnict.setCount(0);
        startTime = System.currentTimeMillis();
        for(int i =0;i<jnict.MAX_TIMES;i++){
            jnict.addCountByInits();
        }
        time = (System.currentTimeMillis() - startTime);
        System.out.println("JNI initsID function:"+time+"ms");
    }
}
  • Native代码
jfieldID JNICacheTest_initsID;

JNIEXPORT void JNICALL Java_JNICacheTest_addCountByInits
(JNIEnv *env, jobject obj){
    jint count = env->GetIntField(obj, JNICacheTest_initsID);
    count++;
    env->SetIntField(obj, JNICacheTest_initsID, count);
}

JNIEXPORT void JNICALL Java_JNICacheTest_initsID
(JNIEnv *env, jclass jcl){
    JNICacheTest_initsID = env->GetFieldID(jcl, "count", "I");
}

JNIEXPORT void JNICALL Java_JNICacheTest_addCount
(JNIEnv *env, jobject obj){
    jclass jcl = env->GetObjectClass(obj);
    jfieldID jid = env->GetFieldID(jcl, "count", "I");

    jint count = env->GetIntField(obj, jid);
    count++;
    env->SetIntField(obj, jid, count);
}

JNIEXPORT void JNICALL Java_JNICacheTest_addCountByCache
(JNIEnv *env, jobject obj){
    jclass jcl = env->GetObjectClass(obj);
    static jfieldID jid = env->GetFieldID(jcl, "count", "I");

    jint count = env->GetIntField(obj, jid);
    count++;
    env->SetIntField(obj, jid, count);
}
  • 运行结果
    这里写图片描述

      不难看出,最快的还是Java-Java访问,然后是Java-JNI缓存,最后是Java-JNI,而且使用了缓存技术之后,速度提升了80%之多,由此可见,在访问JNI接口频率较高的程序中,缓存的使用必不可少。
      

5 总结

  其实两者没有本质的区别,都是将之前的jmethodID和jfieldID类型的变量设置成静态的,根据发生的时刻不同,再分为使用时和类静态初始化时,目的都是为了搜索ID的时间消耗。
  使用时缓存技术不需要更改Java源代码,代码量也比较少,程序员需要对每一个ID变量进行检查,如果需要访问的ID比较少的话,这会是一个不错的选择。
  如果能够对Java源代码进行更改的话,推荐优先使用类初始化的缓存技术,这样做更有利于ID的管理和使用。而且,方法和字段的ID依赖于类的存在,使用类初始化进行缓存,将会动态的更新ID值。

猜你喜欢

转载自blog.csdn.net/cv_jason/article/details/79810563