1 #![cfg(feature = "invocation")]
2 #![feature(test)]
3 
4 extern crate test;
5 
6 use jni_sys::jvalue;
7 use lazy_static::lazy_static;
8 
9 use jni::{
10     descriptors::Desc,
11     objects::{JClass, JMethodID, JObject, JStaticMethodID, JValue},
12     signature::{Primitive, ReturnType},
13     sys::jint,
14     InitArgsBuilder, JNIEnv, JNIVersion, JavaVM,
15 };
16 
17 static CLASS_MATH: &str = "java/lang/Math";
18 static CLASS_OBJECT: &str = "java/lang/Object";
19 static CLASS_LOCAL_DATE_TIME: &str = "java/time/LocalDateTime";
20 static METHOD_MATH_ABS: &str = "abs";
21 static METHOD_OBJECT_HASH_CODE: &str = "hashCode";
22 static METHOD_CTOR: &str = "<init>";
23 static METHOD_LOCAL_DATE_TIME_OF: &str = "of";
24 static SIG_OBJECT_CTOR: &str = "()V";
25 static SIG_MATH_ABS: &str = "(I)I";
26 static SIG_OBJECT_HASH_CODE: &str = "()I";
27 static SIG_LOCAL_DATE_TIME_OF: &str = "(IIIIIII)Ljava/time/LocalDateTime;";
28 
29 #[inline(never)]
native_abs(x: i32) -> i3230 fn native_abs(x: i32) -> i32 {
31     x.abs()
32 }
33 
jni_abs_safe(env: &JNIEnv, x: jint) -> jint34 fn jni_abs_safe(env: &JNIEnv, x: jint) -> jint {
35     let x = JValue::from(x);
36     let v = env
37         .call_static_method(CLASS_MATH, METHOD_MATH_ABS, SIG_MATH_ABS, &[x])
38         .unwrap();
39     v.i().unwrap()
40 }
41 
jni_hash_safe(env: &JNIEnv, obj: JObject) -> jint42 fn jni_hash_safe(env: &JNIEnv, obj: JObject) -> jint {
43     let v = env
44         .call_method(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE, &[])
45         .unwrap();
46     v.i().unwrap()
47 }
48 
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>49 fn jni_local_date_time_of_safe<'e>(
50     env: &JNIEnv<'e>,
51     year: jint,
52     month: jint,
53     day_of_month: jint,
54     hour: jint,
55     minute: jint,
56     second: jint,
57     nanosecond: jint,
58 ) -> JObject<'e> {
59     let v = env
60         .call_static_method(
61             CLASS_LOCAL_DATE_TIME,
62             METHOD_LOCAL_DATE_TIME_OF,
63             SIG_LOCAL_DATE_TIME_OF,
64             &[
65                 year.into(),
66                 month.into(),
67                 day_of_month.into(),
68                 hour.into(),
69                 minute.into(),
70                 second.into(),
71                 nanosecond.into(),
72             ],
73         )
74         .unwrap();
75     v.l().unwrap()
76 }
77 
jni_int_call_static_unchecked<'c, C>( env: &JNIEnv<'c>, class: C, method_id: JStaticMethodID, x: jint, ) -> jint where C: Desc<'c, JClass<'c>>,78 fn jni_int_call_static_unchecked<'c, C>(
79     env: &JNIEnv<'c>,
80     class: C,
81     method_id: JStaticMethodID,
82     x: jint,
83 ) -> jint
84 where
85     C: Desc<'c, JClass<'c>>,
86 {
87     let x = JValue::from(x);
88     let ret = ReturnType::Primitive(Primitive::Int);
89     let v = env
90         .call_static_method_unchecked(class, method_id, ret, &[x.into()])
91         .unwrap();
92     v.i().unwrap()
93 }
94 
jni_int_call_unchecked<'m, M>(env: &JNIEnv<'m>, obj: JObject<'m>, method_id: M) -> jint where M: Desc<'m, JMethodID>,95 fn jni_int_call_unchecked<'m, M>(env: &JNIEnv<'m>, obj: JObject<'m>, method_id: M) -> jint
96 where
97     M: Desc<'m, JMethodID>,
98 {
99     let ret = ReturnType::Primitive(Primitive::Int);
100     let v = env.call_method_unchecked(obj, method_id, ret, &[]).unwrap();
101     v.i().unwrap()
102 }
103 
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>>,104 fn jni_object_call_static_unchecked<'c, C>(
105     env: &JNIEnv<'c>,
106     class: C,
107     method_id: JStaticMethodID,
108     args: &[jvalue],
109 ) -> JObject<'c>
110 where
111     C: Desc<'c, JClass<'c>>,
112 {
113     let v = env
114         .call_static_method_unchecked(class, method_id, ReturnType::Object, args)
115         .unwrap();
116     v.l().unwrap()
117 }
118 
119 #[cfg(test)]
120 mod tests {
121     use super::*;
122     use std::rc::Rc;
123     use std::sync::Arc;
124     use test::{black_box, Bencher};
125 
126     lazy_static! {
127         static ref VM: JavaVM = {
128             let args = InitArgsBuilder::new()
129                 .version(JNIVersion::V8)
130                 .build()
131                 .unwrap();
132             JavaVM::new(args).unwrap()
133         };
134     }
135 
136     #[bench]
native_call_function(b: &mut Bencher)137     fn native_call_function(b: &mut Bencher) {
138         b.iter(|| {
139             let _ = native_abs(black_box(-3));
140         });
141     }
142 
143     #[bench]
jni_call_static_abs_method_safe(b: &mut Bencher)144     fn jni_call_static_abs_method_safe(b: &mut Bencher) {
145         let env = VM.attach_current_thread().unwrap();
146 
147         b.iter(|| jni_abs_safe(&env, -3));
148     }
149 
150     #[bench]
jni_call_static_abs_method_unchecked_str(b: &mut Bencher)151     fn jni_call_static_abs_method_unchecked_str(b: &mut Bencher) {
152         let env = VM.attach_current_thread().unwrap();
153         let class = CLASS_MATH;
154         let method_id = env
155             .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS)
156             .unwrap();
157 
158         b.iter(|| jni_int_call_static_unchecked(&env, class, method_id, -3));
159     }
160 
161     #[bench]
jni_call_static_abs_method_unchecked_jclass(b: &mut Bencher)162     fn jni_call_static_abs_method_unchecked_jclass(b: &mut Bencher) {
163         let env = VM.attach_current_thread().unwrap();
164         let class: JClass = CLASS_MATH.lookup(&env).unwrap();
165         let method_id = env
166             .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS)
167             .unwrap();
168 
169         b.iter(|| jni_int_call_static_unchecked(&env, class, method_id, -3));
170     }
171 
172     #[bench]
jni_call_static_date_time_method_safe(b: &mut Bencher)173     fn jni_call_static_date_time_method_safe(b: &mut Bencher) {
174         let env = VM.attach_current_thread().unwrap();
175         b.iter(|| {
176             let obj = jni_local_date_time_of_safe(&env, 1, 1, 1, 1, 1, 1, 1);
177             env.delete_local_ref(obj).unwrap();
178         });
179     }
180 
181     #[bench]
jni_call_static_date_time_method_unchecked_jclass(b: &mut Bencher)182     fn jni_call_static_date_time_method_unchecked_jclass(b: &mut Bencher) {
183         let env = VM.attach_current_thread().unwrap();
184         let class: JClass = CLASS_LOCAL_DATE_TIME.lookup(&env).unwrap();
185         let method_id = env
186             .get_static_method_id(class, METHOD_LOCAL_DATE_TIME_OF, SIG_LOCAL_DATE_TIME_OF)
187             .unwrap();
188 
189         b.iter(|| {
190             let obj = jni_object_call_static_unchecked(
191                 &env,
192                 class,
193                 method_id,
194                 &[
195                     JValue::Int(1).into(),
196                     JValue::Int(1).into(),
197                     JValue::Int(1).into(),
198                     JValue::Int(1).into(),
199                     JValue::Int(1).into(),
200                     JValue::Int(1).into(),
201                     JValue::Int(1).into(),
202                 ],
203             );
204             env.delete_local_ref(obj).unwrap();
205         });
206     }
207 
208     #[bench]
jni_call_object_hash_method_safe(b: &mut Bencher)209     fn jni_call_object_hash_method_safe(b: &mut Bencher) {
210         let env = VM.attach_current_thread().unwrap();
211         let s = env.new_string("").unwrap();
212         let obj = black_box(JObject::from(s));
213 
214         b.iter(|| jni_hash_safe(&env, obj));
215     }
216 
217     #[bench]
jni_call_object_hash_method_unchecked(b: &mut Bencher)218     fn jni_call_object_hash_method_unchecked(b: &mut Bencher) {
219         let env = VM.attach_current_thread().unwrap();
220         let s = env.new_string("").unwrap();
221         let obj = black_box(JObject::from(s));
222         let method_id = env
223             .get_method_id(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE)
224             .unwrap();
225 
226         b.iter(|| jni_int_call_unchecked(&env, obj, method_id));
227     }
228 
229     #[bench]
jni_new_object_str(b: &mut Bencher)230     fn jni_new_object_str(b: &mut Bencher) {
231         let env = VM.attach_current_thread().unwrap();
232         let class = CLASS_OBJECT;
233 
234         b.iter(|| {
235             let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
236             env.delete_local_ref(obj).unwrap();
237         });
238     }
239 
240     #[bench]
jni_new_object_by_id_str(b: &mut Bencher)241     fn jni_new_object_by_id_str(b: &mut Bencher) {
242         let env = VM.attach_current_thread().unwrap();
243         let class = CLASS_OBJECT;
244         let ctor_id = env
245             .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR)
246             .unwrap();
247 
248         b.iter(|| {
249             let obj = env.new_object_unchecked(class, ctor_id, &[]).unwrap();
250             env.delete_local_ref(obj).unwrap();
251         });
252     }
253 
254     #[bench]
jni_new_object_jclass(b: &mut Bencher)255     fn jni_new_object_jclass(b: &mut Bencher) {
256         let env = VM.attach_current_thread().unwrap();
257         let class: JClass = CLASS_OBJECT.lookup(&env).unwrap();
258 
259         b.iter(|| {
260             let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
261             env.delete_local_ref(obj).unwrap();
262         });
263     }
264 
265     #[bench]
jni_new_object_by_id_jclass(b: &mut Bencher)266     fn jni_new_object_by_id_jclass(b: &mut Bencher) {
267         let env = VM.attach_current_thread().unwrap();
268         let class: JClass = CLASS_OBJECT.lookup(&env).unwrap();
269         let ctor_id = env
270             .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR)
271             .unwrap();
272 
273         b.iter(|| {
274             let obj = env.new_object_unchecked(class, ctor_id, &[]).unwrap();
275             env.delete_local_ref(obj).unwrap();
276         });
277     }
278 
279     #[bench]
jni_new_global_ref(b: &mut Bencher)280     fn jni_new_global_ref(b: &mut Bencher) {
281         let env = VM.attach_current_thread().unwrap();
282         let class = CLASS_OBJECT;
283         let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
284         let global_ref = env.new_global_ref(obj).unwrap();
285         env.delete_local_ref(obj).unwrap();
286 
287         b.iter(|| env.new_global_ref(&global_ref).unwrap());
288     }
289 
290     /// Checks the overhead of checking if exception has occurred.
291     ///
292     /// Such checks are required each time a Java method is called, but
293     /// can be omitted if we call a JNI method that returns an error status.
294     ///
295     /// See also #58
296     #[bench]
jni_check_exception(b: &mut Bencher)297     fn jni_check_exception(b: &mut Bencher) {
298         let env = VM.attach_current_thread().unwrap();
299 
300         b.iter(|| env.exception_check().unwrap());
301     }
302 
303     #[bench]
jni_get_java_vm(b: &mut Bencher)304     fn jni_get_java_vm(b: &mut Bencher) {
305         let env = VM.attach_current_thread().unwrap();
306 
307         b.iter(|| {
308             let _jvm = env.get_java_vm().unwrap();
309         });
310     }
311 
312     /// A benchmark measuring Push/PopLocalFrame overhead.
313     ///
314     /// Such operations are *required* if one attaches a long-running
315     /// native thread to the JVM because there is no 'return-from-native-method'
316     /// event when created local references are freed, hence no way for
317     /// the JVM to know that the local references are no longer used in the native code.
318     #[bench]
jni_noop_with_local_frame(b: &mut Bencher)319     fn jni_noop_with_local_frame(b: &mut Bencher) {
320         // Local frame size actually doesn't matter since JVM does not preallocate anything.
321         const LOCAL_FRAME_SIZE: i32 = 32;
322         let env = VM.attach_current_thread().unwrap();
323         b.iter(|| {
324             env.with_local_frame(LOCAL_FRAME_SIZE, || Ok(JObject::null()))
325                 .unwrap()
326         });
327     }
328 
329     /// A benchmark of the overhead of attaching and detaching a native thread.
330     ///
331     /// It is *huge* — two orders of magnitude higher than calling a single
332     /// Java method using unchecked APIs (e.g., `jni_call_static_unchecked`).
333     ///
334     #[bench]
jvm_noop_attach_detach_native_thread(b: &mut Bencher)335     fn jvm_noop_attach_detach_native_thread(b: &mut Bencher) {
336         b.iter(|| {
337             let env = VM.attach_current_thread().unwrap();
338             black_box(&env);
339         });
340     }
341 
342     #[bench]
native_arc(b: &mut Bencher)343     fn native_arc(b: &mut Bencher) {
344         let env = VM.attach_current_thread().unwrap();
345         let class = CLASS_OBJECT;
346         let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
347         let global_ref = env.new_global_ref(obj).unwrap();
348         env.delete_local_ref(obj).unwrap();
349         let arc = Arc::new(global_ref);
350 
351         b.iter(|| {
352             let _ = black_box(Arc::clone(&arc));
353         });
354     }
355 
356     #[bench]
native_rc(b: &mut Bencher)357     fn native_rc(b: &mut Bencher) {
358         let _env = VM.attach_current_thread().unwrap();
359         let env = VM.get_env().unwrap();
360         let rc = Rc::new(env);
361 
362         b.iter(|| {
363             let _ = black_box(Rc::clone(&rc));
364         });
365     }
366 }
367