if ((*env)->ExceptionCheck(env)) { return ERR_CALL_STATIC_METHOD_FAILED; }
当我们每次希望调用方法时查找类和方法 ID 都会产生六个本机调用,而不是第一次缓存类和方法 ID 时需要的两个调用。
缓存会对您应用程序的运行时造成显著的影响。考虑下面两个版本的方法,它们的作用是相同的。清单 2 使用了缓存的字段 ID:
清单 2. 使用缓存的字段 ID - int sumValues2(JNIEnv* env, jobject obj, jobject allValues){
- jint avalue = (*env)->GetIntField(env, allValues, a);
- jint bvalue = (*env)->GetIntField(env, allValues, b);
- jint cvalue = (*env)->GetIntField(env, allValues, c);
- jint dvalue = (*env)->GetIntField(env, allValues, d);
- jint evalue = (*env)->GetIntField(env, allValues, e);
- jint fvalue = (*env)->GetIntField(env, allValues, f);
- return avalue + bvalue + cvalue + dvalue + evalue + fvalue;
- }
清单 3. 未缓存字段 ID - int sumValues2(JNIEnv* env, jobject obj, jobject allValues){
- jclass cls = (*env)->GetObjectClass(env,allValues);
- jfieldID a = (*env)->GetFieldID(env, cls, "a", "I");
- jfieldID b = (*env)->GetFieldID(env, cls, "b", "I");
- jfieldID c = (*env)->GetFieldID(env, cls, "c", "I");
- jfieldID d = (*env)->GetFieldID(env, cls, "d", "I");
- jfieldID e = (*env)->GetFieldID(env, cls, "e", "I");
- jfieldID f = (*env)->GetFieldID(env, cls, "f", "I");
- jint avalue = (*env)->GetIntField(env, allValues, a);
- jint bvalue = (*env)->GetIntField(env, allValues, b);
- jint cvalue = (*env)->GetIntField(env, allValues, c);
- jint dvalue = (*env)->GetIntField(env, allValues, d);
- jint evalue = (*env)->GetIntField(env, allValues, e);
- jint fvalue = (*env)->GetIntField(env, allValues, f);
- return avalue + bvalue + cvalue + dvalue + evalue + fvalue
清单 2 用 3,572 ms 运行了 10,000,000 次。清单 3 用了 86,217 ms — 多花了 24 倍的时间。
触发数组副本
JNI 在 Java 代码和本机代码之间提供了一个干净的接口。为了维持这种分离,数组将作为不透明的句柄传递,并且本机代码必须回调 JVM 以便使用 set 和 get 调用操作数组元素。Java 规范让 JVM 实现决定让这些调用提供对数组的直接访问,还是返回一个数组副本。举例来说,当数组经过优化而不需要连续存储时,JVM 可以返回一个副本。(参见 参考资料 获取关于 JVM 的信息)。
随后,这些调用可以复制被操作的元素。举例来说,如果您对含有 1,000 个元素的数组调用 GetLongArrayElements(),则会造成至少分配或复制 8,000 字节的数据(每个 long 1,000 元素 * 8 字节)。当您随后使用 ReleaseLongArrayElements() 更新数组的内容时,需要另外复制 8,000 字节的数据来更新数组。即使您使用较新的 GetPrimitiveArrayCritical(),规范仍然准许 JVM 创建完整数组的副本。
GetTypeArrayRegion() 和 SetTypeArrayRegion() 方法允许您获取和更新数组的一部分,而不是整个数组。通过使用这些方法访问较大的数组,您可以确保只复制本机代码将要实际使用的数组部分。 |