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