Skip to content

Commit

Permalink
Android: lazy initialization for method id.
Browse files Browse the repository at this point in the history
Rather than requiring early registration for all method id, we can initialize
them lazily as required.
This solves the problem of building against SDK X but running against X - 1.
Also adds a microbenchmark to ensure there are no considerable regressions.
Results are a bit variable, but it hovers over:
[ERROR:jni_android_unittest.cc(125)] JNI LazyMethodIDCall (us) 1983
[ERROR:jni_android_unittest.cc(127)] JNI MethodIDCall (us) 1862

BUG=152987
TEST=JNIAndroidMicrobenchmark.MethodId
TBR=akalin

Review URL: https://chromiumcodereview.appspot.com/11038015

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@162186 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
bulach@chromium.org committed Oct 16, 2012
1 parent 784ba78 commit c410a2c
Show file tree
Hide file tree
Showing 24 changed files with 588 additions and 598 deletions.
148 changes: 56 additions & 92 deletions base/android/jni_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@

#include "base/android/build_info.h"
#include "base/android/jni_string.h"
#include "base/atomicops.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/threading/platform_thread.h"

namespace {
using base::android::GetClass;
using base::android::GetMethodID;
using base::android::MethodID;
using base::android::ScopedJavaLocalRef;

struct MethodIdentifier {
Expand Down Expand Up @@ -58,17 +57,20 @@ std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) {
ScopedJavaLocalRef<jclass> throwable_clazz =
GetClass(env, "java/lang/Throwable");
jmethodID throwable_printstacktrace =
GetMethodID(env, throwable_clazz, "printStackTrace",
"(Ljava/io/PrintStream;)V");
MethodID::Get<MethodID::TYPE_INSTANCE>(
env, throwable_clazz.obj(), "printStackTrace",
"(Ljava/io/PrintStream;)V");

// Create an instance of ByteArrayOutputStream.
ScopedJavaLocalRef<jclass> bytearray_output_stream_clazz =
GetClass(env, "java/io/ByteArrayOutputStream");
jmethodID bytearray_output_stream_constructor =
GetMethodID(env, bytearray_output_stream_clazz, "<init>", "()V");
MethodID::Get<MethodID::TYPE_INSTANCE>(
env, bytearray_output_stream_clazz.obj(), "<init>", "()V");
jmethodID bytearray_output_stream_tostring =
GetMethodID(env, bytearray_output_stream_clazz, "toString",
"()Ljava/lang/String;");
MethodID::Get<MethodID::TYPE_INSTANCE>(
env, bytearray_output_stream_clazz.obj(), "toString",
"()Ljava/lang/String;");
ScopedJavaLocalRef<jobject> bytearray_output_stream(env,
env->NewObject(bytearray_output_stream_clazz.obj(),
bytearray_output_stream_constructor));
Expand All @@ -77,8 +79,9 @@ std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) {
ScopedJavaLocalRef<jclass> printstream_clazz =
GetClass(env, "java/io/PrintStream");
jmethodID printstream_constructor =
GetMethodID(env, printstream_clazz, "<init>",
"(Ljava/io/OutputStream;)V");
MethodID::Get<MethodID::TYPE_INSTANCE>(
env, printstream_clazz.obj(), "<init>",
"(Ljava/io/OutputStream;)V");
ScopedJavaLocalRef<jobject> printstream(env,
env->NewObject(printstream_clazz.obj(), printstream_constructor,
bytearray_output_stream.obj()));
Expand All @@ -96,35 +99,6 @@ std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) {
return ConvertJavaStringToUTF8(exception_string);
}

enum MethodType {
METHODTYPE_STATIC,
METHODTYPE_NORMAL,
};

enum ExceptionCheck {
EXCEPTIONCHECK_YES,
EXCEPTIONCHECK_NO,
};

template<MethodType method_type, ExceptionCheck exception_check>
jmethodID GetMethodIDInternal(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature) {
jmethodID method_id = method_type == METHODTYPE_STATIC ?
env->GetStaticMethodID(clazz, method_name, jni_signature) :
env->GetMethodID(clazz, method_name, jni_signature);
if (exception_check == EXCEPTIONCHECK_YES) {
CHECK(!base::android::ClearException(env) && method_id) <<
"Failed to find " <<
(method_type == METHODTYPE_STATIC ? "static " : "") <<
"method " << method_name << " " << jni_signature;
} else if (base::android::HasException(env)) {
env->ExceptionClear();
}
return method_id;
}

} // namespace

namespace base {
Expand Down Expand Up @@ -181,68 +155,57 @@ bool HasClass(JNIEnv* env, const char* class_name) {
return true;
}

jmethodID GetMethodID(JNIEnv* env,
const JavaRef<jclass>& clazz,
const char* method_name,
const char* jni_signature) {
// clazz.env() can not be used as that may be from a different thread.
return GetMethodID(env, clazz.obj(), method_name, jni_signature);
}

jmethodID GetMethodID(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature) {
return GetMethodIDInternal<METHODTYPE_NORMAL, EXCEPTIONCHECK_YES>(
env, clazz, method_name, jni_signature);
template<MethodID::Type type>
jmethodID MethodID::Get(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature) {
jmethodID id = type == TYPE_STATIC ?
env->GetStaticMethodID(clazz, method_name, jni_signature) :
env->GetMethodID(clazz, method_name, jni_signature);
CHECK(base::android::ClearException(env) || id) <<
"Failed to find " <<
(type == TYPE_STATIC ? "static " : "") <<
"method " << method_name << " " << jni_signature;
return id;
}

jmethodID GetMethodIDOrNull(JNIEnv* env,
// If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call
// into ::Get() above. If there's a race, it's ok since the values are the same
// (and the duplicated effort will happen only once).
template<MethodID::Type type>
jmethodID MethodID::LazyGet(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature) {
return GetMethodIDInternal<METHODTYPE_NORMAL, EXCEPTIONCHECK_NO>(
env, clazz, method_name, jni_signature);
const char* jni_signature,
base::subtle::AtomicWord* atomic_method_id) {
COMPILE_ASSERT(sizeof(subtle::AtomicWord) >= sizeof(jmethodID),
AtomicWord_SmallerThan_jMethodID);
subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_method_id);
if (value)
return reinterpret_cast<jmethodID>(value);
jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature);
base::subtle::Release_Store(
atomic_method_id, reinterpret_cast<subtle::AtomicWord>(id));
return id;
}

jmethodID GetStaticMethodID(JNIEnv* env,
const JavaRef<jclass>& clazz,
const char* method_name,
const char* jni_signature) {
return GetStaticMethodID(env, clazz.obj(), method_name,
jni_signature);
}
// Various template instantiations.
template jmethodID MethodID::Get<MethodID::TYPE_STATIC>(
JNIEnv* env, jclass clazz, const char* method_name,
const char* jni_signature);

jmethodID GetStaticMethodID(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature) {
return GetMethodIDInternal<METHODTYPE_STATIC, EXCEPTIONCHECK_YES>(
env, clazz, method_name, jni_signature);
}
template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>(
JNIEnv* env, jclass clazz, const char* method_name,
const char* jni_signature);

jmethodID GetStaticMethodIDOrNull(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature) {
return GetMethodIDInternal<METHODTYPE_STATIC, EXCEPTIONCHECK_NO>(
env, clazz, method_name, jni_signature);
}
template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>(
JNIEnv* env, jclass clazz, const char* method_name,
const char* jni_signature, base::subtle::AtomicWord* atomic_method_id);

bool HasMethod(JNIEnv* env,
const JavaRef<jclass>& clazz,
const char* method_name,
const char* jni_signature) {
jmethodID method_id =
env->GetMethodID(clazz.obj(), method_name, jni_signature);
if (!method_id) {
ClearException(env);
return false;
}
bool error = ClearException(env);
DCHECK(!error);
return true;
}
template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>(
JNIEnv* env, jclass clazz, const char* method_name,
const char* jni_signature, base::subtle::AtomicWord* atomic_method_id);

jfieldID GetFieldID(JNIEnv* env,
const JavaRef<jclass>& clazz,
Expand Down Expand Up @@ -308,7 +271,8 @@ jmethodID GetMethodIDFromClassName(JNIEnv* env,
}

ScopedJavaLocalRef<jclass> clazz(env, env->FindClass(class_name));
jmethodID id = GetMethodID(env, clazz, method, jni_signature);
jmethodID id = MethodID::Get<MethodID::TYPE_INSTANCE>(
env, clazz.obj(), method, jni_signature);

while (base::subtle::Acquire_CompareAndSwap(&g_method_id_map_lock,
kUnlocked,
Expand Down
75 changes: 28 additions & 47 deletions base/android/jni_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <sys/types.h>

#include "base/android/scoped_java_ref.h"
#include "base/atomicops.h"
#include "base/compiler_specific.h"

namespace base {
Expand Down Expand Up @@ -57,56 +58,36 @@ jclass GetUnscopedClass(JNIEnv* env, const char* class_name) WARN_UNUSED_RESULT;
// Returns true iff the class |class_name| could be found.
bool HasClass(JNIEnv* env, const char* class_name);

// Returns the method ID for the method with the specified name and signature.
// This method triggers a fatal assertion if the method could not be found.
// Use HasMethod if you need to check whether a method exists.
jmethodID GetMethodID(JNIEnv* env,
const JavaRef<jclass>& clazz,
const char* method_name,
const char* jni_signature);

// Similar to GetMethodID, but takes a raw jclass.
jmethodID GetMethodID(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature);

// Unlike GetMethodID, returns NULL if the method could not be found.
jmethodID GetMethodIDOrNull(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature);

// Returns the method ID for the static method with the specified name and
// signature.
// This method triggers a fatal assertion if the method could not be found.
// Use HasMethod if you need to check whether a method exists.
jmethodID GetStaticMethodID(JNIEnv* env,
const JavaRef<jclass>& clazz,
const char* method_name,
const char* jni_signature);

// Similar to the GetStaticMethodID, but takes a raw jclass.
jmethodID GetStaticMethodID(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature);

// Unlike GetStaticMethodID, returns NULL if the method could not be found.
jmethodID GetStaticMethodIDOrNull(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature);

// Returns true iff |clazz| has a method with the specified name and signature.
bool HasMethod(JNIEnv* env,
const JavaRef<jclass>& clazz,
const char* method_name,
const char* jni_signature);
// This class is a wrapper for JNIEnv Get(Static)MethodID.
class MethodID {
public:
enum Type {
TYPE_STATIC,
TYPE_INSTANCE,
};

// Returns the method ID for the method with the specified name and signature.
// This method triggers a fatal assertion if the method could not be found.
template<Type type>
static jmethodID Get(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature);

// The caller is responsible to zero-initialize |atomic_method_id|.
// It's fine to simultaneously call this on multiple threads referencing the
// same |atomic_method_id|.
template<Type type>
static jmethodID LazyGet(JNIEnv* env,
jclass clazz,
const char* method_name,
const char* jni_signature,
base::subtle::AtomicWord* atomic_method_id);
};

// Gets the method ID from the class name. Clears the pending Java exception
// and returns NULL if the method is not found. Caches results. Note that
// GetMethodID() below avoids a class lookup, but does not cache results.
// MethodID::Get() above avoids a class lookup, but does not cache results.
// Strings passed to this function are held in the cache and MUST remain valid
// beyond the duration of all future calls to this function, across all
// threads. In practice, this means that the function should only be used with
Expand Down
47 changes: 47 additions & 0 deletions base/android/jni_android_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "base/android/jni_android.h"

#include "base/at_exit.h"
#include "base/logging.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
Expand Down Expand Up @@ -90,5 +91,51 @@ TEST_F(JNIAndroidTest, GetMethodIDFromClassNameCaching) {
EXPECT_EQ(g_last_method_id, id3);
}

namespace {

base::subtle::AtomicWord g_atomic_id = 0;
int LazyMethodIDCall(JNIEnv* env, jclass clazz, int p) {
jmethodID id = base::android::MethodID::LazyGet<
base::android::MethodID::TYPE_STATIC>(
env, clazz,
"abs",
"(I)I",
&g_atomic_id);

return env->CallStaticIntMethod(clazz, id, p);
}

int MethodIDCall(JNIEnv* env, jclass clazz, jmethodID id, int p) {
return env->CallStaticIntMethod(clazz, id, p);
}

} // namespace

TEST(JNIAndroidMicrobenchmark, MethodId) {
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jclass> clazz(GetClass(env, "java/lang/Math"));
base::Time start_lazy = base::Time::Now();
int o = 0;
for (int i = 0; i < 1024; ++i)
o += LazyMethodIDCall(env, clazz.obj(), i);
base::Time end_lazy = base::Time::Now();

jmethodID id = reinterpret_cast<jmethodID>(g_atomic_id);
base::Time start = base::Time::Now();
for (int i = 0; i < 1024; ++i)
o += MethodIDCall(env, clazz.obj(), id, i);
base::Time end = base::Time::Now();

// On a Galaxy Nexus, results were in the range of:
// JNI LazyMethodIDCall (us) 1984
// JNI MethodIDCall (us) 1861
LOG(ERROR) << "JNI LazyMethodIDCall (us) " <<
base::TimeDelta(end_lazy - start_lazy).InMicroseconds();
LOG(ERROR) << "JNI MethodIDCall (us) " <<
base::TimeDelta(end - start).InMicroseconds();
LOG(ERROR) << "JNI " << o;
}


} // namespace android
} // namespace base
Loading

0 comments on commit c410a2c

Please sign in to comment.