aboutsummaryrefslogtreecommitdiff
path: root/benches/api_calls.rs
diff options
context:
space:
mode:
Diffstat (limited to 'benches/api_calls.rs')
-rw-r--r--benches/api_calls.rs282
1 files changed, 282 insertions, 0 deletions
diff --git a/benches/api_calls.rs b/benches/api_calls.rs
new file mode 100644
index 0000000..411b536
--- /dev/null
+++ b/benches/api_calls.rs
@@ -0,0 +1,282 @@
+#![cfg(feature = "invocation")]
+#![feature(test)]
+
+extern crate test;
+
+use lazy_static::lazy_static;
+
+use jni::{
+ descriptors::Desc,
+ objects::{JClass, JMethodID, JObject, JStaticMethodID, JValue},
+ signature::{JavaType, Primitive},
+ sys::jint,
+ InitArgsBuilder, JNIEnv, JNIVersion, JavaVM,
+};
+
+static CLASS_MATH: &str = "java/lang/Math";
+static CLASS_OBJECT: &str = "java/lang/Object";
+static METHOD_MATH_ABS: &str = "abs";
+static METHOD_OBJECT_HASH_CODE: &str = "hashCode";
+static METHOD_CTOR: &str = "<init>";
+static SIG_OBJECT_CTOR: &str = "()V";
+static SIG_MATH_ABS: &str = "(I)I";
+static SIG_OBJECT_HASH_CODE: &str = "()I";
+
+#[inline(never)]
+fn native_abs(x: i32) -> i32 {
+ x.abs()
+}
+
+fn jni_abs_safe(env: &JNIEnv, x: jint) -> jint {
+ let x = JValue::from(x);
+ let v = env
+ .call_static_method(CLASS_MATH, METHOD_MATH_ABS, SIG_MATH_ABS, &[x])
+ .unwrap();
+ v.i().unwrap()
+}
+
+fn jni_call_static_unchecked<'c, C>(
+ env: &JNIEnv<'c>,
+ class: C,
+ method_id: JStaticMethodID<'c>,
+ x: jint,
+) -> jint
+where
+ C: Desc<'c, JClass<'c>>,
+{
+ let x = JValue::from(x);
+ let ret = JavaType::Primitive(Primitive::Int);
+ let v = env
+ .call_static_method_unchecked(class, method_id, ret, &[x])
+ .unwrap();
+ v.i().unwrap()
+}
+
+fn jni_hash_safe(env: &JNIEnv, obj: JObject) -> jint {
+ let v = env
+ .call_method(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE, &[])
+ .unwrap();
+ v.i().unwrap()
+}
+
+fn jni_call_unchecked<'m, M>(env: &JNIEnv<'m>, obj: JObject<'m>, method_id: M) -> jint
+where
+ M: Desc<'m, JMethodID<'m>>,
+{
+ let ret = JavaType::Primitive(Primitive::Int);
+ let v = env.call_method_unchecked(obj, method_id, ret, &[]).unwrap();
+ v.i().unwrap()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::rc::Rc;
+ use std::sync::Arc;
+ use test::{black_box, Bencher};
+
+ lazy_static! {
+ static ref VM: JavaVM = {
+ let args = InitArgsBuilder::new()
+ .version(JNIVersion::V8)
+ .build()
+ .unwrap();
+ JavaVM::new(args).unwrap()
+ };
+ }
+
+ #[bench]
+ fn native_call_function(b: &mut Bencher) {
+ b.iter(|| {
+ let _ = native_abs(black_box(-3));
+ });
+ }
+
+ #[bench]
+ fn jni_call_static_method_safe(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+
+ b.iter(|| jni_abs_safe(&env, -3));
+ }
+
+ #[bench]
+ fn jni_call_static_method_unchecked_str(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class = CLASS_MATH;
+ let method_id = env
+ .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS)
+ .unwrap();
+
+ b.iter(|| jni_call_static_unchecked(&env, class, method_id, -3));
+ }
+
+ #[bench]
+ fn jni_call_static_method_unchecked_jclass(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class: JClass = CLASS_MATH.lookup(&env).unwrap();
+ let method_id = env
+ .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS)
+ .unwrap();
+
+ b.iter(|| jni_call_static_unchecked(&env, class, method_id, -3));
+ }
+
+ #[bench]
+ fn jni_call_object_method_safe(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let s = env.new_string("").unwrap();
+ let obj = black_box(JObject::from(s));
+
+ b.iter(|| jni_hash_safe(&env, obj));
+ }
+
+ #[bench]
+ fn jni_call_object_method_unchecked(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let s = env.new_string("").unwrap();
+ let obj = black_box(JObject::from(s));
+ let method_id = env
+ .get_method_id(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE)
+ .unwrap();
+
+ b.iter(|| jni_call_unchecked(&env, obj, method_id));
+ }
+
+ #[bench]
+ fn jni_new_object_str(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class = CLASS_OBJECT;
+
+ b.iter(|| {
+ let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
+ env.delete_local_ref(obj).unwrap();
+ });
+ }
+
+ #[bench]
+ fn jni_new_object_by_id_str(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class = CLASS_OBJECT;
+ let ctor_id = env
+ .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR)
+ .unwrap();
+
+ b.iter(|| {
+ let obj = env.new_object_unchecked(class, ctor_id, &[]).unwrap();
+ env.delete_local_ref(obj).unwrap();
+ });
+ }
+
+ #[bench]
+ fn jni_new_object_jclass(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class: JClass = CLASS_OBJECT.lookup(&env).unwrap();
+
+ b.iter(|| {
+ let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
+ env.delete_local_ref(obj).unwrap();
+ });
+ }
+
+ #[bench]
+ fn jni_new_object_by_id_jclass(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class: JClass = CLASS_OBJECT.lookup(&env).unwrap();
+ let ctor_id = env
+ .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR)
+ .unwrap();
+
+ b.iter(|| {
+ let obj = env.new_object_unchecked(class, ctor_id, &[]).unwrap();
+ env.delete_local_ref(obj).unwrap();
+ });
+ }
+
+ #[bench]
+ fn jni_new_global_ref(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class = CLASS_OBJECT;
+ let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
+ let global_ref = env.new_global_ref(obj).unwrap();
+ env.delete_local_ref(obj).unwrap();
+
+ b.iter(|| env.new_global_ref(&global_ref).unwrap());
+ }
+
+ /// Checks the overhead of checking if exception has occurred.
+ ///
+ /// Such checks are required each time a Java method is called, but
+ /// can be omitted if we call a JNI method that returns an error status.
+ ///
+ /// See also #58
+ #[bench]
+ fn jni_check_exception(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+
+ b.iter(|| env.exception_check().unwrap());
+ }
+
+ #[bench]
+ fn jni_get_java_vm(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+
+ b.iter(|| {
+ let _jvm = env.get_java_vm().unwrap();
+ });
+ }
+
+ /// A benchmark measuring Push/PopLocalFrame overhead.
+ ///
+ /// Such operations are *required* if one attaches a long-running
+ /// native thread to the JVM because there is no 'return-from-native-method'
+ /// event when created local references are freed, hence no way for
+ /// the JVM to know that the local references are no longer used in the native code.
+ #[bench]
+ fn jni_noop_with_local_frame(b: &mut Bencher) {
+ // Local frame size actually doesn't matter since JVM does not preallocate anything.
+ const LOCAL_FRAME_SIZE: i32 = 32;
+ let env = VM.attach_current_thread().unwrap();
+ b.iter(|| {
+ env.with_local_frame(LOCAL_FRAME_SIZE, || Ok(JObject::null()))
+ .unwrap()
+ });
+ }
+
+ /// A benchmark of the overhead of attaching and detaching a native thread.
+ ///
+ /// It is *huge* — two orders of magnitude higher than calling a single
+ /// Java method using unchecked APIs (e.g., `jni_call_static_unchecked`).
+ ///
+ #[bench]
+ fn jvm_noop_attach_detach_native_thread(b: &mut Bencher) {
+ b.iter(|| {
+ let env = VM.attach_current_thread().unwrap();
+ black_box(&env);
+ });
+ }
+
+ #[bench]
+ fn native_arc(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class = CLASS_OBJECT;
+ let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
+ let global_ref = env.new_global_ref(obj).unwrap();
+ env.delete_local_ref(obj).unwrap();
+ let arc = Arc::new(global_ref);
+
+ b.iter(|| {
+ let _ = black_box(Arc::clone(&arc));
+ });
+ }
+
+ #[bench]
+ fn native_rc(b: &mut Bencher) {
+ let _env = VM.attach_current_thread().unwrap();
+ let env = VM.get_env().unwrap();
+ let rc = Rc::new(env);
+
+ b.iter(|| {
+ let _ = black_box(Rc::clone(&rc));
+ });
+ }
+}