367 lines
11 KiB
Rust
367 lines
11 KiB
Rust
#![cfg(feature = "invocation")]
|
|
#![feature(test)]
|
|
|
|
extern crate test;
|
|
|
|
use jni_sys::jvalue;
|
|
use lazy_static::lazy_static;
|
|
|
|
use jni::{
|
|
descriptors::Desc,
|
|
objects::{JClass, JMethodID, JObject, JStaticMethodID, JValue},
|
|
signature::{Primitive, ReturnType},
|
|
sys::jint,
|
|
InitArgsBuilder, JNIEnv, JNIVersion, JavaVM,
|
|
};
|
|
|
|
static CLASS_MATH: &str = "java/lang/Math";
|
|
static CLASS_OBJECT: &str = "java/lang/Object";
|
|
static CLASS_LOCAL_DATE_TIME: &str = "java/time/LocalDateTime";
|
|
static METHOD_MATH_ABS: &str = "abs";
|
|
static METHOD_OBJECT_HASH_CODE: &str = "hashCode";
|
|
static METHOD_CTOR: &str = "<init>";
|
|
static METHOD_LOCAL_DATE_TIME_OF: &str = "of";
|
|
static SIG_OBJECT_CTOR: &str = "()V";
|
|
static SIG_MATH_ABS: &str = "(I)I";
|
|
static SIG_OBJECT_HASH_CODE: &str = "()I";
|
|
static SIG_LOCAL_DATE_TIME_OF: &str = "(IIIIIII)Ljava/time/LocalDateTime;";
|
|
|
|
#[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_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_local_date_time_of_safe<'e>(
|
|
env: &JNIEnv<'e>,
|
|
year: jint,
|
|
month: jint,
|
|
day_of_month: jint,
|
|
hour: jint,
|
|
minute: jint,
|
|
second: jint,
|
|
nanosecond: jint,
|
|
) -> JObject<'e> {
|
|
let v = env
|
|
.call_static_method(
|
|
CLASS_LOCAL_DATE_TIME,
|
|
METHOD_LOCAL_DATE_TIME_OF,
|
|
SIG_LOCAL_DATE_TIME_OF,
|
|
&[
|
|
year.into(),
|
|
month.into(),
|
|
day_of_month.into(),
|
|
hour.into(),
|
|
minute.into(),
|
|
second.into(),
|
|
nanosecond.into(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
v.l().unwrap()
|
|
}
|
|
|
|
fn jni_int_call_static_unchecked<'c, C>(
|
|
env: &JNIEnv<'c>,
|
|
class: C,
|
|
method_id: JStaticMethodID,
|
|
x: jint,
|
|
) -> jint
|
|
where
|
|
C: Desc<'c, JClass<'c>>,
|
|
{
|
|
let x = JValue::from(x);
|
|
let ret = ReturnType::Primitive(Primitive::Int);
|
|
let v = env
|
|
.call_static_method_unchecked(class, method_id, ret, &[x.into()])
|
|
.unwrap();
|
|
v.i().unwrap()
|
|
}
|
|
|
|
fn jni_int_call_unchecked<'m, M>(env: &JNIEnv<'m>, obj: JObject<'m>, method_id: M) -> jint
|
|
where
|
|
M: Desc<'m, JMethodID>,
|
|
{
|
|
let ret = ReturnType::Primitive(Primitive::Int);
|
|
let v = env.call_method_unchecked(obj, method_id, ret, &[]).unwrap();
|
|
v.i().unwrap()
|
|
}
|
|
|
|
fn jni_object_call_static_unchecked<'c, C>(
|
|
env: &JNIEnv<'c>,
|
|
class: C,
|
|
method_id: JStaticMethodID,
|
|
args: &[jvalue],
|
|
) -> JObject<'c>
|
|
where
|
|
C: Desc<'c, JClass<'c>>,
|
|
{
|
|
let v = env
|
|
.call_static_method_unchecked(class, method_id, ReturnType::Object, args)
|
|
.unwrap();
|
|
v.l().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_abs_method_safe(b: &mut Bencher) {
|
|
let env = VM.attach_current_thread().unwrap();
|
|
|
|
b.iter(|| jni_abs_safe(&env, -3));
|
|
}
|
|
|
|
#[bench]
|
|
fn jni_call_static_abs_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_int_call_static_unchecked(&env, class, method_id, -3));
|
|
}
|
|
|
|
#[bench]
|
|
fn jni_call_static_abs_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_int_call_static_unchecked(&env, class, method_id, -3));
|
|
}
|
|
|
|
#[bench]
|
|
fn jni_call_static_date_time_method_safe(b: &mut Bencher) {
|
|
let env = VM.attach_current_thread().unwrap();
|
|
b.iter(|| {
|
|
let obj = jni_local_date_time_of_safe(&env, 1, 1, 1, 1, 1, 1, 1);
|
|
env.delete_local_ref(obj).unwrap();
|
|
});
|
|
}
|
|
|
|
#[bench]
|
|
fn jni_call_static_date_time_method_unchecked_jclass(b: &mut Bencher) {
|
|
let env = VM.attach_current_thread().unwrap();
|
|
let class: JClass = CLASS_LOCAL_DATE_TIME.lookup(&env).unwrap();
|
|
let method_id = env
|
|
.get_static_method_id(class, METHOD_LOCAL_DATE_TIME_OF, SIG_LOCAL_DATE_TIME_OF)
|
|
.unwrap();
|
|
|
|
b.iter(|| {
|
|
let obj = jni_object_call_static_unchecked(
|
|
&env,
|
|
class,
|
|
method_id,
|
|
&[
|
|
JValue::Int(1).into(),
|
|
JValue::Int(1).into(),
|
|
JValue::Int(1).into(),
|
|
JValue::Int(1).into(),
|
|
JValue::Int(1).into(),
|
|
JValue::Int(1).into(),
|
|
JValue::Int(1).into(),
|
|
],
|
|
);
|
|
env.delete_local_ref(obj).unwrap();
|
|
});
|
|
}
|
|
|
|
#[bench]
|
|
fn jni_call_object_hash_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_hash_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_int_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));
|
|
});
|
|
}
|
|
}
|