JNI/NDK Development Guide (9) -- JNI call Performance Test and optimization, jnindk
Reprinted please indicate the source: http://blog.csdn.net/xyang81/article/details/44279725
In the previous chapters, we learned to declare an native method in Java, then generate a prototype Declaration of the Local interface function, and then use C/C ++ to implement these functions, and generate a dynamic shared library for the corresponding platform and place it under the class path of the Java program, finally, calling the declared native method in the Java program indirectly calls the function written in C/C ++, programs written in C/C ++ can avoid JVM memory overhead restrictions, handle high-performance computing, call system services, and other functions. At the same time, I learned how to call any methods and object attributes in the Java program through the interfaces provided by JNI in the local code. This is some of the advantages provided by JNI. However, all the children's shoes that have done Java should understand that Java programs run on JVM, so when Java calls a cross-language interface such as C/C ++ or other languages, or when you access the methods or attributes of objects in Java through the JNI interface in C/C ++ code, the performance is very low compared to calling your own methods in Java !!! Some online users targetJava calls the local interface and calls the Java methodA detailed test is conducted to fully demonstrate that JNI brings advantages to the program and also accepts the performance overhead. See a set of test data below:
Java calls JNI empty function and Java calls Java empty method performance test
Test environment: JDK1.4.2 _ 19, JDK1.5.0 _ 04, and JDK1.6.0 _ 14. The number of tests is 0.1 billion. The absolute value of the test result is of little significance and is for reference only. According to the performance of the JVM and the machine, the values produced by the test will also be different, but no matter what machine or JVM should be able to reflect the same problem, Java calls the native interface, the performance of calling Java methods is much lower than that of calling Java methods.
Performance of Java calling Java empty methods:
JDK version |
Java debugging time consumption |
Average number of calls per second |
1.6 |
329 ms |
303951367 times |
1.5 |
312 ms |
320512820 times |
1.4 |
312 ms |
27233115 times |
Performance of calling JNI empty functions in Java:
JDK version |
JNI debugging time in Java |
Average number of calls per second |
1.6 |
1531 ms |
65316786 times |
1.5 |
1891 ms |
52882072 times |
1.4 |
3672 ms |
27233115 times |
From the above test data, we can see that the higher the JDK version, the better the performance of JNI calls. In JDK1.5, the performance of JNI is nearly five times slower than that of Java internal calls, and more than ten times slower than that of JDK1.4.
JNI search method ID, field ID, Class reference performance test
When we want to access Java object fields or call their methods in the local code, the local code must call FindClass (), GetFieldID (), GetStaticFieldID, GetMethodID () and GetStaticMethodID (). For GetFieldID (), GetStaticFieldID, GetMethodID (), and GetStaticMethodID (), IDS returned for specific classes do not change during the lifetime of the JVM process. However, calling to obtain fields or methods sometimes requires a lot of work in JVM, because fields and methods may be inherited from the superclass, this allows the JVM to traverse the class hierarchy to find them. Because the IDs are the same for a specific class, you only need to find them once and then use them again. Similarly, the overhead of searching class objects is large, so they should also be cached. The following is a test on the Performance of calling the JNI interface FindClass to find the field ID of the Class, GetFieldID to obtain the field ID, and GetFieldValue to obtain the field value.CacheIndicates only one call,No CacheThe corresponding JNI interface is called every time:
Java. version = 1.6.0 _ 14
JNI field read (Cache Class = false, cache field ID = false) Time consumed: 79172 MS average per second: 1263072
JNI field read (Cache Class = true, cache field ID = false) Time consumed: 25015 MS average per second: 3997601
JNI field read (Cache Class = false, cache field ID = true) Time consumed: 50765 MS average per second: 1969861
JNI field read (Cache Class = true, cache field ID = true) Time consumed: 2125 MS average per second: 47058823
Java. version = 1.5.0 _ 04
JNI field read (Cache Class = false, cache field ID = false) Time consumed: 87109 MS average per second: 1147987
JNI field read (Cache Class = true, cache field ID = false) Time consumed: 32031 MS average per second: 3121975
JNI field read (Cache Class = false, cache field ID = true) Time consumed: 51657 MS average per second: 1935846
JNI field read (Cache Class = true, cache field ID = true) Time consumed: 2187 MS average per second: 45724737
Java. version = 1.4.2 _ 19
JNI field read (Cache Class = false, cache field ID = false) Time consumed: 97500 MS average per second: 1025641
JNI field read (Cache Class = true, cache field ID = false) Time consumed: 38110 MS average per second: 2623983
JNI field read (Cache Class = false, cache field ID = true) Time consumed: 55204 MS average per second: 1811462
JNI field read (Cache Class = true, cache field ID = true) Time consumed: 4187 MS average per second: 23883448
According to the test data above, it is found that searching for class and ID (Attribute and method ID) consumes a lot of time. The time for reading the field value is basically an order of magnitude higher than the preceding JNI empty method. If you search for class and field by name every time, the performance will drop as high40 times. The performance of reading a field value is millions, which is intolerable in JNI applications with frequent interactions. The most time consumed is to find the class. Therefore, it is necessary to save the class and member id in native. The class and member id are stable within a certain range. However, under the Dynamically Loaded class loader, The Global class stored may either fail or cause the classloader to be unmounted, special attention should be paid to this issue in JNI applications such as OSGI frameworks. In terms of reading Field Values and searching for FieldID, the gap between JDK1.4 and 1.5 and 1.6 is very obvious. However, there is no significant difference between the three versions in the most time-consuming class search.
From the test above, we can see that when calling the JNI interface to obtain the method ID, field ID, and Class reference, if the cache is not used, the performance will be as low as 4 times. Therefore, in JNI development, the rational use of the cache technology can greatly improve the performance of the program. There are two types of cache: cache in use and cache in class static initialization. The main difference is the cache time.
Cache during use
The field ID, method ID, and Class references are cached when used in the function. The following is an example:
Package com. study. jnilearn; public class AccessCache {private String str = "Hello"; public native void accessField (); // access the str member variable public native String newString (char [] chars, int len); // create a String object public static void main (String [] args) {AccessCache accessCache = new AccessCache (); accessCache Based on the character array and the specified length. nativeMethod (); char chars [] = new char [7]; chars [0] = '中'; chars [1] = 'hua '; chars [2] = 'people'; chars [3] = 'Min'; chars [4] = 'col'; chars [5] = 'and '; chars [6] = 'state'; String str = accessCache. newString (chars, 6); System. out. println (str);} static {System. loadLibrary ("AccessCache ");}}
Header file generated by javah: com_study_jnilearn_AccessCache.h
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_study_jnilearn_AccessCache */#ifndef _Included_com_study_jnilearn_AccessCache#define _Included_com_study_jnilearn_AccessCache#ifdef __cplusplusextern "C" {#endif/* * Class: com_study_jnilearn_AccessCache * Method: accessField * Signature: ()V */JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_accessField(JNIEnv *, jobject);/* * Class: com_study_jnilearn_AccessCache * Method: newString * Signature: ([CI)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_study_jnilearn_AccessCache_newString(JNIEnv *, jobject,jcharArray, jint);#ifdef __cplusplus}#endif#endif
Implement the function in the header file: AccessCache. c
// AccessCache. c # include "com_study_jnilearn_AccessCache.h" JNIEXPORT void JNICALL encode (JNIEnv * env, jobject obj) {// store the field in the memory data zone during the first access and release it until the program ends, static jfieldID fid_str = NULL; jclass cls_AccessCache; jstring j_str; const char * c_str; cls_AccessCache = (* env)-> GetObjectClass (env, obj ); // obtain the Class reference of this object if (cls_AccessCache = NULL) {return;} // first, determine whether the field ID has been cached, if it has already been cached, do not search for if (fid_str = NULL) {fid_str = (* env)-> GetFieldID (env, cls_AccessCache, "str ", "Ljava/lang/String;"); // judge whether the str field of this class is found again if (fid_str = NULL) {return ;}} j_str = (* env) -> GetObjectField (env, obj, fid_str); // obtain the field value c_str = (* env)-> GetStringUTFChars (env, j_str, NULL ); if (c_str = NULL) {return; // insufficient memory} printf ("In C: \ n str = \" % s \ "\ n", c_str ); (* env)-> ReleaseStringUTFChars (env, j_str, c_str); // release the memory space allocated from the JVM. // modify the field value. j_str = (* env) -> NewStringUTF (env, "12345"); if (j_str = NULL) {return;} (* env)-> SetObjectField (env, obj, fid_str, j_str ); // release the local reference (* env)-> DeleteLocalRef (env, cls_AccessCache); (* env)-> DeleteLocalRef (env, j_str );} JNIEXPORT jstring JNICALL encode (JNIEnv * env, jobject obj, jcharArray j_char_arr, jint len) {jcharArray elemArray; jchar * chars = NULL; jstring j_str = NULL; static jclass cls_string = NULL; static jmethodID cid_string = NULL; // The cache String class references if (cls_string = NULL) {cls_string = (* env)-> FindClass (env, "java/lang/String"); if (cls_string = NULL) {return NULL ;}// cache String constructor ID if (cid_string = NULL) {cid_string = (* env)-> GetMethodID (env, cls_string, "<init>", "([C) V"); if (cid_string = NULL) {return NULL ;}} printf ("In C array Len: % d \ n", len); // create a character array elemArray = (* env) -> NewCharArray (env, len); if (elemArray = NULL) {return NULL;} // get the pointer reference of the array. Note: jcharArray cannot be directly used as the final parameter chars = (* env)-> GetCharArrayElements (env, j_char_arr, NULL) of SetCharArrayRegion function; if (chars = NULL) {return NULL ;} // copy the content in the Java character array to the new character array (* env)-> SetCharArrayRegion (env, elemArray, 0, len, chars ); // call the construction method of the String object to create a String object j_str = (* env)-> NewObject (env, cls_string, cid_string, elemArray) with the specified character array as the content ); // release the local reference (* env)-> DeleteLocalRef (env, elemArray); return j_str ;}
Example 1: In row 8th of the Java_com_study_jnilearn_AccessCache_accessField function, a static variable fid_str is defined to store the field ID. Each time a function is called, the field ID is cached in row 18th, if it is not obtained first and saved to fid_str, the variable has a value when it is called again next time. It does not need to be obtained in JVM, which plays the role of caching.
Example 2: two variables cls_string and cid_string are defined in rows 53 and 54 of the Java_com_study_jnilearn_AccessCache_newString function, which are used to store Class references of java. lang. String classes and String constructor IDs respectively. In rows 56 and 64, the system first determines whether the cache has been used, if no, call the JNI interface to obtain the Class reference and constructor ID of the String from the JVM and store it in static variables. The function can be directly used when it is called again next time. It does not need to be searched again, and the cache effect is also achieved.
Class static initialization Cache
Before calling the method or attribute of a class, the Java Virtual Machine checks whether the class has been loaded into the memory. If not, it loads the class first and then calls the static initialization code block of the class, therefore, it is also a good choice to calculate and cache the field ID and method ID in the class during the static initialization process. The following is an example:
package com.study.jnilearn;public class AccessCache { public static native void initIDs(); public native void nativeMethod(); public void callback() { System.out.println("AccessCache.callback invoked!"); } public static void main(String[] args) { AccessCache accessCache = new AccessCache(); accessCache.nativeMethod(); } static { System.loadLibrary("AccessCache"); initIDs(); }}
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_study_jnilearn_AccessCache */#ifndef _Included_com_study_jnilearn_AccessCache#define _Included_com_study_jnilearn_AccessCache#ifdef __cplusplusextern "C" {#endif/* * Class: com_study_jnilearn_AccessCache * Method: initIDs * Signature: ()V */JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_initIDs (JNIEnv *, jclass);/* * Class: com_study_jnilearn_AccessCache * Method: nativeMethod * Signature: ()V */JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_nativeMethod (JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif
// AccessCache.c#include "com_study_jnilearn_AccessCache.h"jmethodID MID_AccessCache_callback;JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_initIDs(JNIEnv *env, jclass cls){ printf("initIDs called!!!\n"); MID_AccessCache_callback = (*env)->GetMethodID(env,cls,"callback","()V");}JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_nativeMethod(JNIEnv *env, jobject obj){ printf("In C Java_com_study_jnilearn_AccessCache_nativeMethod called!!!\n"); (*env)->CallVoidMethod(env, obj, MID_AccessCache_callback);}
JVM loads AccessCache. after the class is in the memory, the static initialization code block of the class is called, that is, the static code block. The System is called first. loadLibrary loads the dynamic library to JVM, and then calls the native method initIDs. It calls the local function Java_com_study_jnilearn_AccessCache_initIDs, obtains the ID to be cached in the function, and stores it in global variables. You can use the global variables directly when using these IDs next time. For example, you can call the Java callback function in line 18.
(*env)->CallVoidMethod(env, obj, MID_AccessCache_callback);
Comparison of Two cache Methods
If you cannot control the source code of the class where the method and field are located when writing the JNI interface, the cache is reasonable during use. However, compared with the Cache during static initialization, the cache has some disadvantages:
1. Check whether the ID or Class reference has been cached every time before use.
2. If the cached ID is used, note that the class will not be unloaded as long as the local code depends on the value of this ID. On the other hand, if the cache occurs during static initialization, when the class is unload or reload, the ID will be recalculated. Because it is recommended that the Field ID, method ID, and Class reference of the Class be cached during static Class initialization.