2010年10月11日 星期一

Caching Field and Method IDs

Obtaining field and method IDs requires symbolic lookups based on the name and descriptor of the field or method. Symbolic lookups are relatively expensive.
How to reduce this overhead?
The idea is to compute field and method IDs and cache them for repeated uses later.
  • Caching at the Point of Use
  • Field and method IDs may be cached at the point where native code accesses the field values or performs method callbacks.
    (Example)InstanceFieldAccess.c: caches the field ID in static variables so that it need not be recomputed upon each invocation of the InstanceFieldAccess.accessField method.
    #include <stdio.h>
    #include <jni.h>
    
    JNIEXPORT void JNICALL 
    Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj)
    {
        static jfieldID fid_s = NULL; /* cached field ID for s */
        jstring jstr;
        const char *str;
    
        /* Get a reference to obj's class */
        jclass cls = (*env)->GetObjectClass(env, obj);
    
        printf("In C:\n");
    
        /* Look for the instance field s in cls */
        if (fid_s == NULL) {
            fid_s = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
            if (fid_s == NULL) {
                return; /* exception already thrown */
            }
        }
    
        /* Read the instance field s */
        jstr = (*env)->GetObjectField(env, obj, fid_s);
        str = (*env)->GetStringUTFChars(env, jstr, NULL);
        if (str == NULL) {
            return; /* out of memory */
        }
        printf("  c.s = \"%s\"\n", str);
        (*env)->ReleaseStringUTFChars(env, jstr, str);
    
        jstr = (*env)->NewStringUTF(env, "123");
        if (jstr == NULL) {
            return; /* out of memory */
        }
        (*env)->SetObjectField(env, obj, fid_s, jstr);
    }
    
    • The static variable fid_s stores the precomputed field ID for "InstanceFieldAccess.s".
    • When the InstanceFieldAccess.accessField method is called for the first time, it computes the field ID and caches it in the static variable for later use.
  • Caching in the Defining Class's Initializer
  • If we use "caching at the Point of Use", it runs into some situations (weakness):
    • It incurs a small performance impact on the "fast path" when the IDs have already been cached.
    • It could lead to duplication of caching and checking.
    It is more convenient to initialize the field and method IDs required by a native method before the application can have a chance to invoke the native method.(Improvement)
    • a suitable place for computing and caching field or method IDs is in the static initializer of the class that defines the fields or methods.
    1. (Example) InstanceMethodCall.java: To cache the method ID for InstanceMethodCall.callback(), we introduce a new native method initIDs that called from the static initializer of the InstanceMethodCall class.
    2. class InstanceMethodCall {
          private static native void initIDs();
          private native void nativeMethod();
          private void callback() {
              System.out.println("In Java");
          }
          public static void main(String args[]) {
              InstanceMethodCall c = new InstanceMethodCall();
              c.nativeMethod();
          }
          static {
              System.loadLibrary("InstanceMethodCall");
              initIDs();
          }
      }
      
    3. Regenerate the C native header file (InstanceMethodCall.h):
    4. /* DO NOT EDIT THIS FILE - it is machine generated */                                                             
      #include <jni.h>
      /* Header for class InstanceMethodCall */
      
      #ifndef _Included_InstanceMethodCall
      #define _Included_InstanceMethodCall
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
       * Class:     InstanceMethodCall
       * Method:    initIDs
       * Signature: ()V
       */
      JNIEXPORT void JNICALL Java_InstanceMethodCall_initIDs
        (JNIEnv *, jclass);
      
      /*
       * Class:     InstanceMethodCall
       * Method:    nativeMethod
       * Signature: ()V
       */
      JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod
        (JNIEnv *, jobject);
      
      #ifdef __cplusplus
      }
      #endif
      #endif
      
    5. C implementation file (InstanceMethodCall.c):
    6. #include <stdio.h>
      #include <jni.h>
      
      jmethodID MID_InstanceMethodCall_callback;
      
          JNIEXPORT void JNICALL
      Java_InstanceMethodCall_initIDs(JNIEnv *env, jclass cls)
      {
          MID_InstanceMethodCall_callback = (*env)->GetMethodID(env, cls, "callback", "()V");
      }
      
          JNIEXPORT void JNICALL
      Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj)
      {
          printf("Output: In C code\n");
          (*env)->CallVoidMethod(env, obj, MID_InstanceMethodCall_callback);
      }
      
      • The implementation of initIDs() simply computes and caches the method ID for InstanceMethodCall.callback().
      • The JVM runs the static initializer, and in turn calls the initIDs() method, before executing any other methods in the InstanceMethodCall class. With the method ID is already cached in a global variable, the native implementation of InstanceMethodCall.nativeMethod() no longer needs to perform a symbolic lookup.
      • RUN the InstanceMethodCall:
      • Output: In C code
        Output: In Java code
  • Comparison between the Two Approaches to Caching IDs
    • Caching IDs at the point of use:
      • (Advantage) - The reasonable solution if the JNI programmer does not have control over the source of the class that defines the field or method.
      • (Disadvantage) -
        1. Requires a check in the execution fast path and may also require duplicated checks and initialization of the same field or method ID.
        2. Method and field IDs are only valid until the class is unloaded.
    • Caching in the Defining Class's Initializer:
      • (Advantage) - Caching is done in the static initializer of the defining class, the cached IDs will automatically be recalculated when the class is unloaded and later reloaded.
  • Performance of JNI Field and Method Operations
    • How does the cost of ...
      1. performing a callback from native code (a Native/Java callback)?
      2. calling a native method (a Java/Native call)?
      3. calling a regular method (a Java/Java call)?
      Java/native calls are potentially slower than Java/Java calls for the following reasons:
      • Native methods most likely follow a different calling convention than that used by Java/Java calls inside the Java virtual machine implementation. As a result, the virtual machine must perform additional operations to build arguments and set up the stack frame before jumping to a native method entry point.
      • It is common for the virtual machine to inline method calls. Inlining Java/native calls is a lot harder than inlining Java/Java calls.
      The typical virtual machine may execute a Java/native call roughly two to three times slower than it executes a Java/Java call.
      In theory, the overhead of native/Java callbacks could also be within two to three times of Java/Java calls.

0 意見: