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