This is the mail archive of the
java-patches@gcc.gnu.org
mailing list for the Java project.
[patch] JNI performance improvements
- From: graydon hoare <graydon at redhat dot com>
- To: java-patches at gcc dot gnu dot org
- Date: Thu, 13 Jan 2005 00:33:00 -0500
- Subject: [patch] JNI performance improvements
hi,
this patch implements two JNI optimizations:
- avoidance of the malloc/free on each JNI call, when the call only
uses the "bottom", non-reentrant frame (this is the majority of JNI
traffic).
- a fast path which avoids clearing the frame when leaving it, if
no local references have been made. most JNI functions do not make
any local references.
remarkably, these two optimizations speed up JNI by about 15x (5x for
the first optimization, 3x for the second). our JNI is now slightly
faster than sun's: on my machine we spend 95ns/call vs. sun's 129ns,
accodring to bryce's benchmark (see bug 12957).
the patch is against java-gui-branch, which is where I've committed it,
but it'd probably be good to copy over to trunk before 4.0 finishes up.
-graydon
2005-01-13 Graydon Hoare <graydon@redhat.com>
* include/jni.h (_Jv_JNIEnv::bottom_locals): New field.
* include/jvm.h (_Jv_FreeJNIEnv): Declare.
* java/lang/natThread.cc (finalize_native): Call _Jv_FreeJNIEnv.
* jni.cc: Reuse bottom frame between calls, avoid clearing
frame when no local references are made.
--- include/jni.h 24 Jun 2004 05:30:14 -0000 1.26.10.1
+++ include/jni.h 13 Jan 2005 05:19:12 -0000
@@ -691,6 +691,10 @@
/* The chain of local frames. */
struct _Jv_JNI_LocalFrame *locals;
+ /* The bottom-most element of the chain, initialized with the env and
+ reused between non-nesting JNI calls. */
+ struct _Jv_JNI_LocalFrame *bottom_locals;
+
public:
jint GetVersion ()
{ return p->GetVersion (this); }
--- include/jvm.h 24 Jun 2004 05:30:14 -0000 1.63.2.1
+++ include/jvm.h 13 Jan 2005 05:19:12 -0000
@@ -475,6 +475,9 @@
_Jv_JNIEnv *_Jv_GetCurrentJNIEnv ();
void _Jv_SetCurrentJNIEnv (_Jv_JNIEnv *);
+/* Free a JNIEnv. */
+void _Jv_FreeJNIEnv (_Jv_JNIEnv *);
+
struct _Jv_JavaVM;
_Jv_JavaVM *_Jv_GetJavaVM ();
--- java/lang/natThread.cc 24 Jun 2004 05:30:28 -0000 1.25.14.1
+++ java/lang/natThread.cc 13 Jan 2005 05:19:12 -0000
@@ -78,6 +78,7 @@
{
natThread *nt = (natThread *) ptr;
_Jv_ThreadDestroyData (nt->thread);
+ _Jv_FreeJNIEnv(nt->jni_env);
}
jint
--- jni.cc 24 Jun 2004 05:29:55 -0000 1.81.2.1
+++ jni.cc 13 Jan 2005 05:19:12 -0000
@@ -69,7 +69,7 @@
// Number of slots in the default frame. The VM must allow at least
// 16.
-#define FRAME_SIZE 32
+#define FRAME_SIZE 16
// Mark value indicating this is an overflow frame.
#define MARK_NONE 0
@@ -83,10 +83,13 @@
{
// This is true if this frame object represents a pushed frame (eg
// from PushLocalFrame).
- int marker : 2;
+ int marker;
+
+ // Flag to indicate some locals were allocated.
+ int allocated_p;
// Number of elements in frame.
- int size : 30;
+ int size;
// Next frame in chain.
_Jv_JNI_LocalFrame *next;
@@ -287,6 +290,7 @@
frame->marker = MARK_NONE;
frame->size = size;
+ frame->allocated_p = 0;
memset (&frame->vec[0], 0, size * sizeof (jobject));
frame->next = env->locals;
env->locals = frame;
@@ -325,6 +329,7 @@
set = true;
done = true;
frame->vec[i] = obj;
+ frame->allocated_p = 1;
break;
}
}
@@ -342,6 +347,7 @@
_Jv_JNI_EnsureLocalCapacity (env, 16);
// We know the first element of the new frame will be ok.
env->locals->vec[0] = obj;
+ env->locals->allocated_p = 1;
}
mark_for_gc (obj, local_ref_table);
@@ -364,12 +370,14 @@
done = (rf->marker == stop);
_Jv_JNI_LocalFrame *n = rf->next;
- // When N==NULL, we've reached the stack-allocated frame, and we
- // must not free it. However, we must be sure to clear all its
- // elements, since we might conceivably reuse it.
+ // When N==NULL, we've reached the reusable bottom_locals, and we must
+ // not free it. However, we must be sure to clear all its elements.
if (n == NULL)
{
- memset (&rf->vec[0], 0, rf->size * sizeof (jobject));
+ if (rf->allocated_p)
+ memset (&rf->vec[0], 0, rf->size * sizeof (jobject));
+ rf->allocated_p = 0;
+ rf = NULL;
break;
}
@@ -410,9 +418,17 @@
extern "C" void
_Jv_JNI_PopSystemFrame (JNIEnv *env)
{
- _Jv_JNI_PopLocalFrame (env, NULL, MARK_SYSTEM);
-
- if (env->ex)
+ // Only enter slow path when we're not at the bottom, or there have been
+ // allocations. Usually this is false and we can just null out the locals
+ // field.
+
+ if (__builtin_expect ((env->locals->next
+ || env->locals->allocated_p), false))
+ _Jv_JNI_PopLocalFrame (env, NULL, MARK_SYSTEM);
+ else
+ env->locals = NULL;
+
+ if (__builtin_expect (env->ex != NULL, false))
{
jthrowable t = env->ex;
env->ex = NULL;
@@ -1864,11 +1880,6 @@
const JNINativeMethod *methods,
jint nMethods)
{
- // Synchronize while we do the work. This must match
- // synchronization in some other functions that manipulate or use
- // the nathash table.
- JvSynchronize sync (global_ref_table);
-
// Look at each descriptor given us, and find the corresponding
// method in the class.
for (int j = 0; j < nMethods; ++j)
@@ -2022,7 +2033,7 @@
_Jv_GetJNIEnvNewFrame (jclass klass)
{
JNIEnv *env = _Jv_GetCurrentJNIEnv ();
- if (env == NULL)
+ if (__builtin_expect (env == NULL, false))
{
env = (JNIEnv *) _Jv_MallocUnchecked (sizeof (JNIEnv));
env->p = &_Jv_JNIFunctions;
@@ -2030,27 +2041,70 @@
env->locals = NULL;
// We set env->ex below.
+ // Set up the bottom, reusable frame.
+ env->bottom_locals = (_Jv_JNI_LocalFrame *)
+ _Jv_MallocUnchecked (sizeof (_Jv_JNI_LocalFrame)
+ + (FRAME_SIZE
+ * sizeof (jobject)));
+
+ env->bottom_locals->marker = MARK_SYSTEM;
+ env->bottom_locals->size = FRAME_SIZE;
+ env->bottom_locals->next = NULL;
+ env->bottom_locals->allocated_p = 0;
+ memset (&env->bottom_locals->vec[0], 0,
+ env->bottom_locals->size * sizeof (jobject));
+
_Jv_SetCurrentJNIEnv (env);
}
- _Jv_JNI_LocalFrame *frame
- = (_Jv_JNI_LocalFrame *) _Jv_MallocUnchecked (sizeof (_Jv_JNI_LocalFrame)
- + (FRAME_SIZE
- * sizeof (jobject)));
+ // If we're in a simple JNI call (non-nested), we can just reuse the
+ // locals frame we allocated many calls ago, back when the env was first
+ // built, above.
- frame->marker = MARK_SYSTEM;
- frame->size = FRAME_SIZE;
- frame->next = env->locals;
+ if (__builtin_expect (env->locals == NULL, true))
+ env->locals = env->bottom_locals;
+
+ else
+ {
+ // Alternatively, we might be re-entering JNI, in which case we can't
+ // reuse the bottom_locals frame, because it is already underneath
+ // us. So we need to make a new one.
+
+ _Jv_JNI_LocalFrame *frame
+ = (_Jv_JNI_LocalFrame *) _Jv_MallocUnchecked (sizeof (_Jv_JNI_LocalFrame)
+ + (FRAME_SIZE
+ * sizeof (jobject)));
+
+ frame->marker = MARK_SYSTEM;
+ frame->size = FRAME_SIZE;
+ frame->allocated_p = 0;
+ frame->next = env->locals;
- for (int i = 0; i < frame->size; ++i)
- frame->vec[i] = NULL;
+ memset (&frame->vec[0], 0,
+ frame->size * sizeof (jobject));
+
+ env->locals = frame;
+ }
- env->locals = frame;
env->ex = NULL;
return env;
}
+// Destroy the env's reusable resources. This is called from the thread
+// destructor "finalize_native" in natThread.cc
+void
+_Jv_FreeJNIEnv (_Jv_JNIEnv *env)
+{
+ if (env == NULL)
+ return;
+
+ if (env->bottom_locals != NULL)
+ _Jv_Free (env->bottom_locals);
+
+ _Jv_Free (env);
+}
+
// Return the function which implements a particular JNI method. If
// we can't find the function, we throw the appropriate exception.
// This is `extern "C"' because the compiler uses it.
@@ -2259,16 +2313,18 @@
env->p = &_Jv_JNIFunctions;
env->ex = NULL;
env->klass = NULL;
- env->locals
+ env->bottom_locals
= (_Jv_JNI_LocalFrame *) _Jv_MallocUnchecked (sizeof (_Jv_JNI_LocalFrame)
+ (FRAME_SIZE
* sizeof (jobject)));
+ env->locals = env->bottom_locals;
if (env->locals == NULL)
{
_Jv_Free (env);
return JNI_ERR;
}
+ env->locals->allocated_p = 0;
env->locals->marker = MARK_SYSTEM;
env->locals->size = FRAME_SIZE;
env->locals->next = NULL;