JNI/NDK開發指南(九)——JNI調用效能測試及最佳化,jnindk

來源:互聯網
上載者:User

JNI/NDK開發指南(九)——JNI調用效能測試及最佳化,jnindk

轉載請註明出處:http://blog.csdn.net/xyang81/article/details/44279725

在前面幾章我們學習到了,在Java中聲明一個native方法,然後產生本地介面的函數原型聲明,再用C/C++實現這些函數,並產生對應平台的動態共用程式庫放到Java程式的類路徑下,最後在Java程式中調用聲明的native方法就間接的調用到了C/C++編寫的函數了,在C/C++中寫的程式可以避開JVM的記憶體開銷過大的限制、處理高效能的計算、調用系統服務等功能。同時也學習到了在本地代碼中通過JNI提供的介面,調用Java程式中的任意方法和對象的屬性。這是JNI提供的一些優勢。但做過Java的童鞋應該都明白,Java程式是運行在JVM上的,所以在Java中調用C/C++或其它語言這種跨語言的介面時,或者說在C/C++代碼中通過JNI介面訪問Java中對象的方法或屬性時,相比Java調用自已的方法,效能是非常低的!!!網上有朋友針對Java調用本地介面,Java調Java方法做了一次詳細的測試,來充分說明在享受JNI給程式帶來優勢的同時,也要接受其所帶來的效能開銷,下面請看一組測試資料:

Java調用JNI空函數與Java調用Java空方法效能測試

測試環境:JDK1.4.2_19、JDK1.5.0_04和JDK1.6.0_14,測試的重複次數都是一億次。測試結果的絕對數值意義不大,僅供參考。因為根據JVM和機器效能的不同,測試所產生的數值也會不同,但不管什麼機器和JVM應該都能反應同一個問題,Java調用native介面,要比Java調用Java方法效能要低很多。

Java調用Java空方法的效能:

JDK版本 Java調Java耗時 平均每秒調用次數
1.6 329ms 303951367次
1.5 312ms 320512820次
1.4 312ms 27233115次

Java調用JNI空函數的效能:

JDK版本 Java調JNI耗時 平均每秒調用次數
1.6 1531ms 65316786次
1.5 1891ms 52882072次
1.4 3672ms 27233115次

從上述測試資料可以看出JDK版本越高,JNI調用的效能也越好。在JDK1.5中,僅僅是空方法調用,JNI的效能就要比Java內部調用慢將近5倍,而在JDK1.4下更是慢了十多倍。

JNI尋找方法ID、欄位ID、Class引用效能測試

當我們在本地代碼中要訪問Java對象的欄位或調用它們的方法時,機器碼必須調用FindClass()、GetFieldID()、GetStaticFieldID、GetMethodID() 和 GetStaticMethodID()。對於 GetFieldID()、GetStaticFieldID、GetMethodID() 和 GetStaticMethodID(),為特定類返回的 ID 不會在 JVM 進程的生存期內發生變化。但是,擷取欄位或方法的調用有時會需要在 JVM 中完成大量工作,因為欄位和方法可能是從超類中繼承而來的,這會讓 JVM 向上遍曆類階層來找到它們。由於 ID 對於特定類是相同的,因此只需要尋找一次,然後便可重複使用。同樣,尋找類對象的開銷也很大,因此也應該緩衝它們。下面對調用JNI介面FindClass尋找Class、GetFieldID擷取類的欄位ID和GetFieldValue擷取欄位的值的效能做的一個測試。緩衝表示只調用一次,不緩衝就是每次都調用相應的JNI介面:
java.version = 1.6.0_14
JNI 欄位讀取 (緩衝Class=false ,緩衝欄位ID=false) 耗時 : 79172 ms 平均每秒 : 1263072
JNI 欄位讀取 (緩衝Class=true ,緩衝欄位ID=false) 耗時 : 25015 ms 平均每秒 : 3997601
JNI 欄位讀取 (緩衝Class=false ,緩衝欄位ID=true) 耗時 : 50765 ms 平均每秒 : 1969861
JNI 欄位讀取 (緩衝Class=true ,緩衝欄位ID=true) 耗時 : 2125 ms 平均每秒 : 47058823
java.version = 1.5.0_04
JNI 欄位讀取 (緩衝Class=false ,緩衝欄位ID=false) 耗時 : 87109 ms 平均每秒 : 1147987
JNI 欄位讀取 (緩衝Class=true ,緩衝欄位ID=false) 耗時 : 32031 ms 平均每秒 : 3121975
JNI 欄位讀取 (緩衝Class=false ,緩衝欄位ID=true) 耗時 : 51657 ms 平均每秒 : 1935846
JNI 欄位讀取 (緩衝Class=true ,緩衝欄位ID=true) 耗時 : 2187 ms 平均每秒 : 45724737
java.version = 1.4.2_19
JNI 欄位讀取 (緩衝Class=false ,緩衝欄位ID=false) 耗時 : 97500 ms 平均每秒 : 1025641
JNI 欄位讀取 (緩衝Class=true ,緩衝欄位ID=false) 耗時 : 38110 ms 平均每秒 : 2623983
JNI 欄位讀取 (緩衝Class=false ,緩衝欄位ID=true) 耗時 : 55204 ms 平均每秒 : 1811462
JNI 欄位讀取 (緩衝Class=true ,緩衝欄位ID=true) 耗時 : 4187 ms 平均每秒 : 23883448
根據上面的測試資料得知,尋找class和ID(屬性和方法ID)消耗的時間比較大。只是讀取欄位值的時間基本上跟上面的JNI空方法是一個數量級。而如果每次都根據名稱尋找class和field的話,效能要下降高達40倍。讀取一個欄位值的效能在百萬級上,在互動頻繁的JNI應用中是不能忍受的。 消耗時間最多的就是尋找class,因此在native裡儲存class和member id是很有必要的。class和member id在一定範圍內是穩定的,但在動態載入的class loader下,儲存全域的class要麼可能失效,要麼可能造成無法卸載classloader,在諸如OSGI架構下的JNI應用還要特別注意這方面的問題。在讀取欄位值和尋找FieldID上,JDK1.4和1.5、1.6的差距是非常明顯的。但在最耗時的尋找class上,三個版本沒有明顯差距。

通過上面的測試可以明顯的看出,在調用JNI介面擷取方法ID、欄位ID和Class引用時,如果沒用使用緩衝的話,效能低至4倍。所以在JNI開發中,合理的使用緩衝技術能給程式提高極大的效能。緩衝有兩種,分別為使用時緩衝和類靜態初始化時緩衝,區別主要在於緩衝發生的時刻。

使用時緩衝

欄位ID、方法ID和Class引用在函數當中使用的同時就緩衝起來。下面看一個樣本:

package com.study.jnilearn;public class AccessCache {    private String str = "Hello";    public native void accessField(); // 訪問str成員變數    public native String newString(char[] chars, int len); // 根據字元數組和指定長度建立String對象    public static void main(String[] args) {        AccessCache accessCache = new AccessCache();        accessCache.nativeMethod();        char chars[] = new char[7];        chars[0] = '中';        chars[1] = '華';        chars[2] = '人';        chars[3] = '民';        chars[4] = '共';        chars[5] = '和';        chars[6] = '國';        String str = accessCache.newString(chars, 6);        System.out.println(str);    }    static {        System.loadLibrary("AccessCache");    }}

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

實現標頭檔中的函數:AccessCache.c

// AccessCache.c#include "com_study_jnilearn_AccessCache.h"JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessCache_accessField  (JNIEnv *env, jobject obj){    // 第一次訪問時將欄位存到記憶體資料區,直到程式結束才會釋放,可以起到緩衝的作用    static jfieldID fid_str = NULL;    jclass cls_AccessCache;    jstring j_str;    const char *c_str;    cls_AccessCache = (*env)->GetObjectClass(env, obj); // 擷取該對象的Class引用    if (cls_AccessCache == NULL) {        return;    }    // 先判斷欄位ID之前是否已經緩衝過,如果已經緩衝過則不進行尋找    if (fid_str == NULL) {        fid_str = (*env)->GetFieldID(env,cls_AccessCache,"str","Ljava/lang/String;");        // 再次判斷是否找到該類的str欄位        if (fid_str == NULL) {            return;        }    }    j_str = (*env)->GetObjectField(env, obj, fid_str);  // 擷取欄位的值    c_str = (*env)->GetStringUTFChars(env, j_str, NULL);    if (c_str == NULL) {        return; // 記憶體不夠    }    printf("In C:\n str = \"%s\"\n", c_str);    (*env)->ReleaseStringUTFChars(env, j_str, c_str);   // 釋放從從JVM新分配字串的記憶體空間    // 修改欄位的值    j_str = (*env)->NewStringUTF(env, "12345");    if (j_str == NULL) {        return;    }    (*env)->SetObjectField(env, obj, fid_str, j_str);    // 釋放本地引用    (*env)->DeleteLocalRef(env,cls_AccessCache);    (*env)->DeleteLocalRef(env,j_str);}JNIEXPORT jstring JNICALL Java_com_study_jnilearn_AccessCache_newString(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;    // 緩衝String的class引用    if (cls_string == NULL) {        cls_string = (*env)->FindClass(env, "java/lang/String");        if (cls_string == NULL) {            return NULL;        }    }    // 緩衝String的構造方法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);    // 建立一個字元數組    elemArray = (*env)->NewCharArray(env, len);    if (elemArray == NULL) {        return NULL;    }    // 擷取數組的指標引用,注意:不能直接將jcharArray作為SetCharArrayRegion函數最後一個參數    chars = (*env)->GetCharArrayElements(env, j_char_arr,NULL);    if (chars == NULL) {        return NULL;    }    // 將Java字元數組中的內容複寫指定長度到新的字元數組中    (*env)->SetCharArrayRegion(env, elemArray, 0, len, chars);    // 調用String對象的構造方法,建立一個指定字元數組為內容的String對象    j_str = (*env)->NewObject(env, cls_string, cid_string, elemArray);    // 釋放本地引用    (*env)->DeleteLocalRef(env, elemArray);    return j_str;}

例1、在Java_com_study_jnilearn_AccessCache_accessField函數中的第8行定義了一個靜態變數fid_str用於儲存欄位的ID,每次調用函數的時候,在第18行先判斷欄位ID是否已經緩衝,如果沒有先取出來存到fid_str中,下次再調用的時候該變數已經有值了,不用再去JVM中擷取,起到了緩衝的作用。

例2、在Java_com_study_jnilearn_AccessCache_newString函數中的53和54行定義了兩個變數cls_string和cid_string,分別用於儲存java.lang.String類的Class引用和String的構造方法ID。在56行和64行處,使用前會先判斷是否已經緩衝過,如果沒有則調用JNI的介面從JVM中擷取String的Class引用和構造方法ID儲存到靜態變數當中。下次再調用該函數時就可以直接使用,不需要再去找一次了,也達到了緩衝的效果。

類靜態初始化緩衝

在調用一個類的方法或屬性之前,Java虛擬機器會先檢查該類是否已經載入到記憶體當中,如果沒有則會先載入,然後緊接著會調用該類的靜態初始化代碼塊,所以在靜態初始化該類的過程當中計算並緩衝該類當中的欄位ID和方法ID也是個不錯的選擇。下面看一個樣本:

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載入AccessCache.class到記憶體當中之後,會調用該類的靜態初始化代碼塊,即static代碼塊,先調用System.loadLibrary載入動態庫到JVM中,緊接著調用native方法initIDs,會調用用到本地函數Java_com_study_jnilearn_AccessCache_initIDs,在該函數中擷取需要緩衝的ID,然後存入全域變數當中。下次需要用到這些ID的時候,直接使用全域變數當中的即可,如18行當中調用Java的callback函數。

(*env)->CallVoidMethod(env, obj, MID_AccessCache_callback);
兩種緩衝方式比較

如果在寫JNI介面時,不能控制方法和欄位所在類的源碼的話,用使用時緩衝比較合理。但比起類靜態初始化時緩衝來說,用使用時緩衝有一些缺點:
1. 使用前,每次都需要檢查是否已經緩衝該ID或Class引用
2. 如果在用使用時緩衝的ID,要注意只要本地代碼依賴於這個ID的值,那麼這個類就不會被unload。另外一方面,如果緩衝發生在靜態初始化時,當類被unload或reload時,ID會被重新計算。因為,盡量在類靜態初始化時就緩衝欄位ID、方法ID和類的Class引用。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.