JNI学习笔记:缓存技术


  • 1 前言
  • 2 使用时缓存
  • 3 类初始化缓存
  • 4 对比程序
  • 5 总结


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);
}
  • 运行结果
    JNI学习笔记:缓存技术_第1张图片

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

5 总结

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

你可能感兴趣的:(JNI,caching,缓存,JNI/C++)