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 // 32 characters
30 static TEST_STRING_UNICODE: &str = "_��㳧~δ��᪘׷ġ˥쩽|ņ/��ٶԦ萴퀉֒ٞHy��%ӓ娎��ăꊦȮ��";
31 
32 #[inline(never)]
native_abs(x: i32) -> i3233 fn native_abs(x: i32) -> i32 {
34     x.abs()
35 }
36 
jni_abs_safe(env: &mut JNIEnv, x: jint) -> jint37 fn jni_abs_safe(env: &mut JNIEnv, x: jint) -> jint {
38     let x = JValue::from(x);
39     let v = env
40         .call_static_method(CLASS_MATH, METHOD_MATH_ABS, SIG_MATH_ABS, &[x])
41         .unwrap();
42     v.i().unwrap()
43 }
44 
jni_hash_safe(env: &mut JNIEnv, obj: &JObject) -> jint45 fn jni_hash_safe(env: &mut JNIEnv, obj: &JObject) -> jint {
46     let v = env
47         .call_method(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE, &[])
48         .unwrap();
49     v.i().unwrap()
50 }
51 
jni_local_date_time_of_safe<'local>( env: &mut JNIEnv<'local>, year: jint, month: jint, day_of_month: jint, hour: jint, minute: jint, second: jint, nanosecond: jint, ) -> JObject<'local>52 fn jni_local_date_time_of_safe<'local>(
53     env: &mut JNIEnv<'local>,
54     year: jint,
55     month: jint,
56     day_of_month: jint,
57     hour: jint,
58     minute: jint,
59     second: jint,
60     nanosecond: jint,
61 ) -> JObject<'local> {
62     let v = env
63         .call_static_method(
64             CLASS_LOCAL_DATE_TIME,
65             METHOD_LOCAL_DATE_TIME_OF,
66             SIG_LOCAL_DATE_TIME_OF,
67             &[
68                 year.into(),
69                 month.into(),
70                 day_of_month.into(),
71                 hour.into(),
72                 minute.into(),
73                 second.into(),
74                 nanosecond.into(),
75             ],
76         )
77         .unwrap();
78     v.l().unwrap()
79 }
80 
jni_int_call_static_unchecked<'local, C>( env: &mut JNIEnv<'local>, class: C, method_id: JStaticMethodID, x: jint, ) -> jint where C: Desc<'local, JClass<'local>>,81 fn jni_int_call_static_unchecked<'local, C>(
82     env: &mut JNIEnv<'local>,
83     class: C,
84     method_id: JStaticMethodID,
85     x: jint,
86 ) -> jint
87 where
88     C: Desc<'local, JClass<'local>>,
89 {
90     let x = JValue::from(x);
91     let ret = ReturnType::Primitive(Primitive::Int);
92     let v =
93         unsafe { env.call_static_method_unchecked(class, method_id, ret, &[x.as_jni()]) }.unwrap();
94     v.i().unwrap()
95 }
96 
jni_int_call_unchecked<'local, M>( env: &mut JNIEnv<'local>, obj: &JObject<'local>, method_id: M, ) -> jint where M: Desc<'local, JMethodID>,97 fn jni_int_call_unchecked<'local, M>(
98     env: &mut JNIEnv<'local>,
99     obj: &JObject<'local>,
100     method_id: M,
101 ) -> jint
102 where
103     M: Desc<'local, JMethodID>,
104 {
105     let ret = ReturnType::Primitive(Primitive::Int);
106     // SAFETY: Caller retrieved method ID + class specifically for this use: Object.hashCode()I
107     let v = unsafe { env.call_method_unchecked(obj, method_id, ret, &[]) }.unwrap();
108     v.i().unwrap()
109 }
110 
jni_object_call_static_unchecked<'local, C>( env: &mut JNIEnv<'local>, class: C, method_id: JStaticMethodID, args: &[jvalue], ) -> JObject<'local> where C: Desc<'local, JClass<'local>>,111 fn jni_object_call_static_unchecked<'local, C>(
112     env: &mut JNIEnv<'local>,
113     class: C,
114     method_id: JStaticMethodID,
115     args: &[jvalue],
116 ) -> JObject<'local>
117 where
118     C: Desc<'local, JClass<'local>>,
119 {
120     // SAFETY: Caller retrieved method ID and constructed arguments
121     let v = unsafe { env.call_static_method_unchecked(class, method_id, ReturnType::Object, args) }
122         .unwrap();
123     v.l().unwrap()
124 }
125 
126 #[cfg(test)]
127 mod tests {
128     use super::*;
129     use jni::objects::GlobalRef;
130     use std::rc::Rc;
131     use std::sync::Arc;
132     use test::{black_box, Bencher};
133 
134     lazy_static! {
135         static ref VM: JavaVM = {
136             let args = InitArgsBuilder::new()
137                 .version(JNIVersion::V8)
138                 .build()
139                 .unwrap();
140             JavaVM::new(args).unwrap()
141         };
142     }
143 
144     #[bench]
native_call_function(b: &mut Bencher)145     fn native_call_function(b: &mut Bencher) {
146         b.iter(|| {
147             let _ = native_abs(black_box(-3));
148         });
149     }
150 
151     #[bench]
jni_call_static_abs_method_safe(b: &mut Bencher)152     fn jni_call_static_abs_method_safe(b: &mut Bencher) {
153         let mut env = VM.attach_current_thread().unwrap();
154 
155         b.iter(|| jni_abs_safe(&mut env, -3));
156     }
157 
158     #[bench]
jni_call_static_abs_method_unchecked_str(b: &mut Bencher)159     fn jni_call_static_abs_method_unchecked_str(b: &mut Bencher) {
160         let mut env = VM.attach_current_thread().unwrap();
161         let class = CLASS_MATH;
162         let method_id = env
163             .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS)
164             .unwrap();
165 
166         b.iter(|| jni_int_call_static_unchecked(&mut env, class, method_id, -3));
167     }
168 
169     #[bench]
jni_call_static_abs_method_unchecked_jclass(b: &mut Bencher)170     fn jni_call_static_abs_method_unchecked_jclass(b: &mut Bencher) {
171         let mut env = VM.attach_current_thread().unwrap();
172         let class = Desc::<JClass>::lookup(CLASS_MATH, &mut env).unwrap();
173         let method_id = env
174             .get_static_method_id(&class, METHOD_MATH_ABS, SIG_MATH_ABS)
175             .unwrap();
176 
177         b.iter(|| jni_int_call_static_unchecked(&mut env, &class, method_id, -3));
178     }
179 
180     #[bench]
jni_call_static_date_time_method_safe(b: &mut Bencher)181     fn jni_call_static_date_time_method_safe(b: &mut Bencher) {
182         let mut env = VM.attach_current_thread().unwrap();
183         b.iter(|| {
184             let obj = jni_local_date_time_of_safe(&mut env, 1, 1, 1, 1, 1, 1, 1);
185             env.delete_local_ref(obj).unwrap();
186         });
187     }
188 
189     #[bench]
jni_call_static_date_time_method_unchecked_jclass(b: &mut Bencher)190     fn jni_call_static_date_time_method_unchecked_jclass(b: &mut Bencher) {
191         let mut env = VM.attach_current_thread().unwrap();
192         let class = Desc::<JClass>::lookup(CLASS_LOCAL_DATE_TIME, &mut env).unwrap();
193         let method_id = env
194             .get_static_method_id(&class, METHOD_LOCAL_DATE_TIME_OF, SIG_LOCAL_DATE_TIME_OF)
195             .unwrap();
196 
197         b.iter(|| {
198             let obj = jni_object_call_static_unchecked(
199                 &mut env,
200                 &class,
201                 method_id,
202                 &[
203                     JValue::Int(1).as_jni(),
204                     JValue::Int(1).as_jni(),
205                     JValue::Int(1).as_jni(),
206                     JValue::Int(1).as_jni(),
207                     JValue::Int(1).as_jni(),
208                     JValue::Int(1).as_jni(),
209                     JValue::Int(1).as_jni(),
210                 ],
211             );
212             env.delete_local_ref(obj).unwrap();
213         });
214     }
215 
216     #[bench]
jni_call_object_hash_method_safe(b: &mut Bencher)217     fn jni_call_object_hash_method_safe(b: &mut Bencher) {
218         let mut env = VM.attach_current_thread().unwrap();
219         let s = env.new_string("").unwrap();
220         let obj = black_box(JObject::from(s));
221 
222         b.iter(|| jni_hash_safe(&mut env, &obj));
223     }
224 
225     #[bench]
jni_call_object_hash_method_unchecked(b: &mut Bencher)226     fn jni_call_object_hash_method_unchecked(b: &mut Bencher) {
227         let mut env = VM.attach_current_thread().unwrap();
228         let s = env.new_string("").unwrap();
229         let obj = black_box(JObject::from(s));
230         let obj_class = env.get_object_class(&obj).unwrap();
231         let method_id = env
232             .get_method_id(&obj_class, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE)
233             .unwrap();
234 
235         b.iter(|| jni_int_call_unchecked(&mut env, &obj, method_id));
236     }
237 
238     #[bench]
jni_new_object_str(b: &mut Bencher)239     fn jni_new_object_str(b: &mut Bencher) {
240         let mut env = VM.attach_current_thread().unwrap();
241         let class = CLASS_OBJECT;
242 
243         b.iter(|| {
244             let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
245             env.delete_local_ref(obj).unwrap();
246         });
247     }
248 
249     #[bench]
jni_new_object_by_id_str(b: &mut Bencher)250     fn jni_new_object_by_id_str(b: &mut Bencher) {
251         let mut env = VM.attach_current_thread().unwrap();
252         let class = CLASS_OBJECT;
253         let ctor_id = env
254             .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR)
255             .unwrap();
256 
257         b.iter(|| {
258             let obj = unsafe { env.new_object_unchecked(class, ctor_id, &[]) }.unwrap();
259             env.delete_local_ref(obj).unwrap();
260         });
261     }
262 
263     #[bench]
jni_new_object_jclass(b: &mut Bencher)264     fn jni_new_object_jclass(b: &mut Bencher) {
265         let mut env = VM.attach_current_thread().unwrap();
266         let class = Desc::<JClass>::lookup(CLASS_OBJECT, &mut env).unwrap();
267 
268         b.iter(|| {
269             let obj = env.new_object(&class, SIG_OBJECT_CTOR, &[]).unwrap();
270             env.delete_local_ref(obj).unwrap();
271         });
272     }
273 
274     #[bench]
jni_new_object_by_id_jclass(b: &mut Bencher)275     fn jni_new_object_by_id_jclass(b: &mut Bencher) {
276         let mut env = VM.attach_current_thread().unwrap();
277         let class = Desc::<JClass>::lookup(CLASS_OBJECT, &mut env).unwrap();
278         let ctor_id = env
279             .get_method_id(&class, METHOD_CTOR, SIG_OBJECT_CTOR)
280             .unwrap();
281 
282         b.iter(|| {
283             let obj = unsafe { env.new_object_unchecked(&class, ctor_id, &[]) }.unwrap();
284             env.delete_local_ref(obj).unwrap();
285         });
286     }
287 
288     #[bench]
jni_new_global_ref(b: &mut Bencher)289     fn jni_new_global_ref(b: &mut Bencher) {
290         let mut env = VM.attach_current_thread().unwrap();
291         let class = CLASS_OBJECT;
292         let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
293         let global_ref = env.new_global_ref(&obj).unwrap();
294         env.delete_local_ref(obj).unwrap();
295 
296         b.iter(|| env.new_global_ref(&global_ref).unwrap());
297     }
298 
299     /// Checks the overhead of checking if exception has occurred.
300     ///
301     /// Such checks are required each time a Java method is called, but
302     /// can be omitted if we call a JNI method that returns an error status.
303     ///
304     /// See also #58
305     #[bench]
jni_check_exception(b: &mut Bencher)306     fn jni_check_exception(b: &mut Bencher) {
307         let env = VM.attach_current_thread().unwrap();
308 
309         b.iter(|| env.exception_check().unwrap());
310     }
311 
312     #[bench]
jni_get_java_vm(b: &mut Bencher)313     fn jni_get_java_vm(b: &mut Bencher) {
314         let env = VM.attach_current_thread().unwrap();
315 
316         b.iter(|| {
317             let _jvm = env.get_java_vm().unwrap();
318         });
319     }
320 
321     #[bench]
jni_get_string(b: &mut Bencher)322     fn jni_get_string(b: &mut Bencher) {
323         let mut env = VM.attach_current_thread().unwrap();
324         let string = env.new_string(TEST_STRING_UNICODE).unwrap();
325 
326         b.iter(|| {
327             let s: String = env.get_string(&string).unwrap().into();
328             assert_eq!(s, TEST_STRING_UNICODE);
329         });
330     }
331 
332     #[bench]
jni_get_string_unchecked(b: &mut Bencher)333     fn jni_get_string_unchecked(b: &mut Bencher) {
334         let env = VM.attach_current_thread().unwrap();
335         let string = env.new_string(TEST_STRING_UNICODE).unwrap();
336 
337         b.iter(|| {
338             let s: String = unsafe { env.get_string_unchecked(&string) }.unwrap().into();
339             assert_eq!(s, TEST_STRING_UNICODE);
340         });
341     }
342 
343     /// A benchmark measuring Push/PopLocalFrame overhead.
344     ///
345     /// Such operations are *required* if one attaches a long-running
346     /// native thread to the JVM because there is no 'return-from-native-method'
347     /// event when created local references are freed, hence no way for
348     /// the JVM to know that the local references are no longer used in the native code.
349     #[bench]
jni_noop_with_local_frame(b: &mut Bencher)350     fn jni_noop_with_local_frame(b: &mut Bencher) {
351         // Local frame size actually doesn't matter since JVM does not preallocate anything.
352         const LOCAL_FRAME_SIZE: i32 = 32;
353         let mut env = VM.attach_current_thread().unwrap();
354         b.iter(|| {
355             env.with_local_frame(LOCAL_FRAME_SIZE, |_| -> Result<_, jni::errors::Error> {
356                 Ok(())
357             })
358             .unwrap()
359         });
360     }
361 
362     /// A benchmark measuring Push/PopLocalFrame overhead while retuning a local reference
363     #[bench]
jni_with_local_frame_returning_local(b: &mut Bencher)364     fn jni_with_local_frame_returning_local(b: &mut Bencher) {
365         // Local frame size actually doesn't matter since JVM does not preallocate anything.
366         const LOCAL_FRAME_SIZE: i32 = 32;
367         let mut env = VM.attach_current_thread().unwrap();
368 
369         let class = env.find_class(CLASS_OBJECT).unwrap();
370         b.iter(|| {
371             env.with_local_frame_returning_local(LOCAL_FRAME_SIZE, |env| {
372                 env.new_object(&class, SIG_OBJECT_CTOR, &[])
373             })
374         });
375     }
376 
377     /// A benchmark measuring Push/PopLocalFrame overhead while retuning a global
378     /// object reference that then gets converted into a local reference before
379     /// dropping the global
380     #[bench]
jni_with_local_frame_returning_global_to_local(b: &mut Bencher)381     fn jni_with_local_frame_returning_global_to_local(b: &mut Bencher) {
382         // Local frame size actually doesn't matter since JVM does not preallocate anything.
383         const LOCAL_FRAME_SIZE: i32 = 32;
384         let mut env = VM.attach_current_thread().unwrap();
385 
386         let class = env.find_class(CLASS_OBJECT).unwrap();
387         b.iter(|| {
388             let global = env
389                 .with_local_frame::<_, GlobalRef, jni::errors::Error>(LOCAL_FRAME_SIZE, |env| {
390                     let local = env.new_object(&class, SIG_OBJECT_CTOR, &[])?;
391                     let global = env.new_global_ref(local)?;
392                     Ok(global)
393                 })
394                 .unwrap();
395             let _local = env.new_local_ref(global).unwrap();
396         });
397     }
398 
399     /// A benchmark of the overhead of attaching and detaching a native thread.
400     ///
401     /// It is *huge* — two orders of magnitude higher than calling a single
402     /// Java method using unchecked APIs (e.g., `jni_call_static_unchecked`).
403     ///
404     #[bench]
jvm_noop_attach_detach_native_thread(b: &mut Bencher)405     fn jvm_noop_attach_detach_native_thread(b: &mut Bencher) {
406         b.iter(|| {
407             let env = VM.attach_current_thread().unwrap();
408             black_box(&env);
409         });
410     }
411 
412     #[bench]
native_arc(b: &mut Bencher)413     fn native_arc(b: &mut Bencher) {
414         let mut env = VM.attach_current_thread().unwrap();
415         let class = CLASS_OBJECT;
416         let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
417         let global_ref = env.new_global_ref(&obj).unwrap();
418         env.delete_local_ref(obj).unwrap();
419         let arc = Arc::new(global_ref);
420 
421         b.iter(|| {
422             let _ = black_box(Arc::clone(&arc));
423         });
424     }
425 
426     #[bench]
native_rc(b: &mut Bencher)427     fn native_rc(b: &mut Bencher) {
428         let _env = VM.attach_current_thread().unwrap();
429         let env = VM.get_env().unwrap();
430         let rc = Rc::new(env);
431 
432         b.iter(|| {
433             let _ = black_box(Rc::clone(&rc));
434         });
435     }
436 }
437