1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/jni_zero/benchmarks/benchmark.h"
6
7 #include <stdint.h>
8
9 #include <atomic>
10
11 #include "base/android/jni_array.h"
12 #include "base/android/jni_string.h"
13 #include "base/logging.h"
14 #include "base/numerics/safe_conversions.h"
15 #include "base/strings/strcat.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/timer/elapsed_timer.h"
18 #include "third_party/google_benchmark/src/include/benchmark/benchmark.h"
19 #include "third_party/jni_zero/benchmarks/benchmark_header/Benchmark_jni.h"
20 #include "third_party/jni_zero/benchmarks/system_jni_headers/Integer_jni.h"
21 #include "third_party/jni_zero/jni_zero.h"
22
23 using benchmark::DoNotOptimize;
24 using jni_zero::JavaParamRef;
25 using jni_zero::JavaRef;
26 using jni_zero::ScopedJavaLocalRef;
27
28 template <>
FromJniType(JNIEnv * env,const jni_zero::JavaRef<jobject> & j_integer)29 std::int32_t jni_zero::FromJniType<std::int32_t>(
30 JNIEnv* env,
31 const jni_zero::JavaRef<jobject>& j_integer) {
32 return static_cast<std::int32_t>(
33 JNI_Integer::Java_Integer_intValue(env, j_integer));
34 }
35 template <>
ToJniType(JNIEnv * env,const std::int32_t & input)36 jni_zero::ScopedJavaLocalRef<jobject> jni_zero::ToJniType<std::int32_t>(
37 JNIEnv* env,
38 const std::int32_t& input) {
39 return JNI_Integer::Java_Integer_valueOf__int(env, static_cast<jint>(input));
40 }
41
42 const int kUsToNs = 1000;
43
44 namespace jni_zero::benchmark {
45
LookupClass(JNIEnv * env,const char * class_name)46 jclass LookupClass(JNIEnv* env, const char* class_name) {
47 std::atomic<jclass> cached_class;
48 return jni_zero::internal::LazyGetClass(env, class_name, &cached_class);
49 }
50
LookupMethod(JNIEnv * env,jclass clazz,const char * method_name,const char * method_signature)51 jmethodID LookupMethod(JNIEnv* env,
52 jclass clazz,
53 const char* method_name,
54 const char* method_signature) {
55 std::atomic<jmethodID> cached_method_id;
56 jni_zero::internal::JniJavaCallContext<true> call_context;
57 call_context.Init<jni_zero::MethodID::TYPE_STATIC>(
58 env, clazz, method_name, method_signature, &cached_method_id);
59 return call_context.method_id();
60 }
61
JNI_Benchmark_RunLookupBenchmark(JNIEnv * env)62 static std::string JNI_Benchmark_RunLookupBenchmark(JNIEnv* env) {
63 std::stringstream benchmark_log;
64 const int kNumTries = 10000;
65 base::ElapsedTimer timer;
66 for (int i = 0; i < kNumTries; i++) {
67 Java_Benchmark_callMe(env);
68 }
69 double delta = timer.Elapsed().InMicrosecondsF();
70
71 benchmark_log << "Called java method [Native -> Java] (" << kNumTries
72 << " times): Elapsed time = " << delta << " us\n";
73 double average_us = delta / kNumTries;
74 benchmark_log << "Average per method call = " << average_us * kUsToNs
75 << " ns\n";
76 return benchmark_log.str();
77 }
78
JNI_Benchmark_RunGeneratedClassesBenchmark(JNIEnv * env)79 static std::string JNI_Benchmark_RunGeneratedClassesBenchmark(JNIEnv* env) {
80 std::stringstream benchmark_log;
81 static constexpr std::string_view kClassPrefix =
82 "org/jni_zero/benchmark/Placeholder";
83 static const char kMethodName[] = "callMe";
84 static const char kMethodSignature[] = "()V";
85 const int kNumClasses = 1000;
86 std::string class_names[kNumClasses];
87 for (int i = 1; i <= kNumClasses; i++) {
88 class_names[i] = base::StrCat({kClassPrefix, base::NumberToString(i)});
89 }
90
91 jclass clazzes[kNumClasses];
92 base::ElapsedTimer timer;
93 for (int i = 1; i <= kNumClasses; i++) {
94 clazzes[i] = LookupClass(env, class_names[i].data());
95 }
96 double elapsed_us = timer.Elapsed().InMicrosecondsF();
97 benchmark_log << "Found different clazz (" << kNumClasses
98 << " times): Elapsed time = " << elapsed_us << " us\n";
99
100 elapsed_us = 0;
101 timer = {};
102 for (int i = 1; i <= kNumClasses; i++) {
103 LookupMethod(env, clazzes[i], kMethodName, kMethodSignature);
104 }
105 elapsed_us = timer.Elapsed().InMicrosecondsF();
106 benchmark_log << "Found different method (" << kNumClasses
107 << " times): Elapsed time = " << elapsed_us << " us\n";
108 return benchmark_log.str();
109 }
110
JNI_Benchmark_RunNativeToJavaParamSizesBenchmark(JNIEnv * env)111 static std::string JNI_Benchmark_RunNativeToJavaParamSizesBenchmark(
112 JNIEnv* env) {
113 std::stringstream benchmark_log;
114 const int kArraySize = 10000;
115 const int kNumTries = 1000;
116 std::vector<int> array;
117 array.resize(kArraySize);
118 for (int i = 0; i < kArraySize; i++) {
119 array[i] = i;
120 }
121
122 base::ElapsedTimer timer;
123 for (int tries = 0; tries < kNumTries; tries++) {
124 Java_Benchmark_receiveLargeIntArray(env, array);
125 }
126 double elapsed_us = timer.Elapsed().InMicrosecondsF();
127 benchmark_log << "Sending " << kArraySize
128 << " int vector with conversion [Native -> Java] (" << kNumTries
129 << " times): Elapsed time = " << elapsed_us << " us\n";
130 double average_us = elapsed_us / kNumTries;
131 benchmark_log << "Average per " << kArraySize
132 << " int vector = " << average_us * kUsToNs << " ns\n";
133
134 elapsed_us = 0;
135 timer = {};
136 for (int tries = 0; tries < kNumTries; tries++) {
137 for (int i = 0; i < kArraySize; i++) {
138 Java_Benchmark_receiveSingleInt(env, i);
139 }
140 }
141 elapsed_us = timer.Elapsed().InMicrosecondsF();
142 benchmark_log << "Sending " << kArraySize
143 << " ints one at a time [Native -> Java] (" << kNumTries
144 << " times): Elapsed time = " << elapsed_us << " us\n";
145 average_us = elapsed_us / kNumTries;
146 benchmark_log << "Average per " << kArraySize
147 << " ints = " << average_us * kUsToNs << " ns\n";
148
149 elapsed_us = 0;
150 timer = {};
151 for (int tries = 0; tries < kNumTries; tries++) {
152 for (int i = 0; i < kArraySize; i++) {
153 Java_Benchmark_receiveSingleInteger(env, i);
154 }
155 }
156 elapsed_us = timer.Elapsed().InMicrosecondsF();
157 benchmark_log << "Sending " << kArraySize
158 << " Integers with conversion one at a time [Native -> Java] ("
159 << kNumTries << " times): Elapsed time = " << elapsed_us
160 << " us\n";
161 average_us = elapsed_us / kNumTries;
162 benchmark_log << "Average per " << kArraySize
163 << " Integers = " << average_us * kUsToNs << " ns\n";
164 return benchmark_log.str();
165 }
166
JNI_Benchmark_RunAttachCurrentThreadBenchmark(JNIEnv * unused)167 static std::string JNI_Benchmark_RunAttachCurrentThreadBenchmark(
168 JNIEnv* unused) {
169 std::stringstream benchmark_log;
170 const int kNumTries = 10000;
171 base::ElapsedTimer timer;
172 for (int tries = 0; tries < kNumTries; tries++) {
173 AttachCurrentThread();
174 }
175 double elapsed_us = timer.Elapsed().InMicrosecondsF();
176 benchmark_log << "Calling AttachCurrentThread (" << kNumTries
177 << " times): Elapsed time = " << elapsed_us << " us\n";
178 double average_us = elapsed_us / kNumTries;
179 benchmark_log << "Average per call = " << average_us * kUsToNs << " ns\n";
180 return benchmark_log.str();
181 }
182
JNI_Benchmark_RunIntegerBoxingBenchmark(JNIEnv * env)183 static std::string JNI_Benchmark_RunIntegerBoxingBenchmark(JNIEnv* env) {
184 std::stringstream benchmark_log;
185 const int kNumTries = 10000;
186 ScopedJavaLocalRef<jobject> j_integerArray[kNumTries];
187 base::ElapsedTimer timer;
188 for (int i = 0; i < kNumTries; i++) {
189 j_integerArray[i] = JNI_Integer::Java_Integer_valueOf__int(env, i);
190 }
191 double elapsed_us = timer.Elapsed().InMicrosecondsF();
192 benchmark_log << "Calling Integer.valueOf (" << kNumTries
193 << " times): Elapsed time = " << elapsed_us << " us\n";
194 double average_us = elapsed_us / kNumTries;
195 benchmark_log << "Average per call = " << average_us * kUsToNs << " ns\n";
196
197 elapsed_us = 0;
198 timer = {};
199 for (int i = 0; i < kNumTries; i++) {
200 JNI_Integer::Java_Integer_intValue(env, j_integerArray[i]);
201 }
202 elapsed_us = timer.Elapsed().InMicrosecondsF();
203 benchmark_log << "Calling Integer.intValue (" << kNumTries
204 << " times): Elapsed time = " << elapsed_us << " us\n";
205 average_us = elapsed_us / kNumTries;
206 benchmark_log << "Average per call = " << average_us * kUsToNs << " ns\n";
207 return benchmark_log.str();
208 }
209
JNI_Benchmark_RunNativeToJavaMultipleParamsBenchmark(JNIEnv * env)210 static std::string JNI_Benchmark_RunNativeToJavaMultipleParamsBenchmark(
211 JNIEnv* env) {
212 std::stringstream benchmark_log;
213 const int kNumTries = 10000;
214 base::ElapsedTimer timer;
215 for (int i = 0; i < kNumTries; i++) {
216 Java_Benchmark_receive10Ints(env, i, i, i, i, i, i, i, i, i, i);
217 }
218 double elapsed_us = timer.Elapsed().InMicrosecondsF();
219 benchmark_log << "Sending 10 ints [Native -> Java] (" << kNumTries
220 << " times): Elapsed time = " << elapsed_us << " us\n";
221 double average_us = elapsed_us / kNumTries;
222 benchmark_log << "Average per 10 ints = " << average_us * kUsToNs << " ns\n";
223
224 elapsed_us = 0;
225 timer = {};
226 for (int i = 0; i < kNumTries; i++) {
227 Java_Benchmark_receive10IntegersConverted(env, i, i, i, i, i, i, i, i, i,
228 i);
229 }
230 elapsed_us = timer.Elapsed().InMicrosecondsF();
231 benchmark_log
232 << "Sending 10 Integers converted with @JniType [Native -> Java] ("
233 << kNumTries << " times): Elapsed time = " << elapsed_us << " us\n";
234 average_us = elapsed_us / kNumTries;
235 benchmark_log << "Average per 10 Integers = " << average_us * kUsToNs
236 << " ns\n";
237 return benchmark_log.str();
238 }
239
JNI_Benchmark_RunNativeToJavaStringsBenchmark(JNIEnv * env)240 static std::string JNI_Benchmark_RunNativeToJavaStringsBenchmark(JNIEnv* env) {
241 std::stringstream benchmark_log;
242 const int kNumTries = 10000;
243 const int kStringSize = 1000;
244 std::string u8_ascii_string = "";
245 std::string u8_non_ascii_string = "";
246 std::u16string u16_ascii_string = u"";
247 std::u16string u16_non_ascii_string = u"";
248 for (int i = 0; i < kStringSize; i++) {
249 u8_ascii_string += "a";
250 u8_non_ascii_string += "ق";
251 u16_ascii_string += u"a";
252 u16_non_ascii_string += u"ق";
253 }
254
255 base::ElapsedTimer timer;
256 for (int i = 0; i < kNumTries; i++) {
257 Java_Benchmark_receiveU8String(env, u8_ascii_string);
258 }
259 double elapsed_us = timer.Elapsed().InMicrosecondsF();
260 benchmark_log << "Sending " << kStringSize
261 << " chars utf-8 ASCII string [Native -> Java] (" << kNumTries
262 << " times): Elapsed time = " << elapsed_us << " us\n";
263 double average_us = elapsed_us / kNumTries;
264 benchmark_log << "Average per " << kStringSize
265 << " char string = " << average_us * kUsToNs << " ns\n";
266
267 elapsed_us = 0;
268 timer = {};
269 for (int i = 0; i < kNumTries; i++) {
270 Java_Benchmark_receiveU16String(env, u16_ascii_string);
271 }
272 elapsed_us = timer.Elapsed().InMicrosecondsF();
273 benchmark_log << "Sending " << kStringSize
274 << " chars utf-16 ASCII string [Native -> Java] (" << kNumTries
275 << " times): Elapsed time = " << elapsed_us << " us\n";
276 average_us = elapsed_us / kNumTries;
277 benchmark_log << "Average per " << kStringSize
278 << " char string = " << average_us * kUsToNs << " ns\n";
279
280 elapsed_us = 0;
281 timer = {};
282 for (int i = 0; i < kNumTries; i++) {
283 Java_Benchmark_receiveU8String(env, u8_non_ascii_string);
284 }
285 elapsed_us = timer.Elapsed().InMicrosecondsF();
286 benchmark_log << "Sending " << kStringSize
287 << " chars utf-8 non-ASCII string [Native -> Java] ("
288 << kNumTries << " times): Elapsed time = " << elapsed_us
289 << " us\n";
290 average_us = elapsed_us / kNumTries;
291 benchmark_log << "Average per " << kStringSize
292 << " char string = " << average_us * kUsToNs << " ns\n";
293
294 elapsed_us = 0;
295 timer = {};
296 for (int i = 0; i < kNumTries; i++) {
297 Java_Benchmark_receiveU16String(env, u16_non_ascii_string);
298 }
299 elapsed_us = timer.Elapsed().InMicrosecondsF();
300 benchmark_log << "Sending " << kStringSize
301 << " chars utf-16 non-ASCII string [Native -> Java] ("
302 << kNumTries << " times): Elapsed time = " << elapsed_us
303 << " us\n";
304 average_us = elapsed_us / kNumTries;
305 benchmark_log << "Average per " << kStringSize
306 << " char string = " << average_us * kUsToNs << " ns\n";
307 return benchmark_log.str();
308 }
309
JNI_Benchmark_SendLargeIntArray(JNIEnv * env,const jni_zero::JavaParamRef<jintArray> & j_array)310 void JNI_Benchmark_SendLargeIntArray(
311 JNIEnv* env,
312 const jni_zero::JavaParamRef<jintArray>& j_array) {
313 size_t array_size = static_cast<size_t>(env->GetArrayLength(j_array.obj()));
314 jint* array = env->GetIntArrayElements(j_array.obj(), nullptr);
315 for (size_t i = 0; i < array_size; i++) {
316 DoNotOptimize(array[i]);
317 }
318 env->ReleaseIntArrayElements(j_array.obj(), array, 0);
319 }
320
JNI_Benchmark_SendLargeIntArrayConverted(JNIEnv * env,std::vector<int32_t> & array)321 static void JNI_Benchmark_SendLargeIntArrayConverted(
322 JNIEnv* env,
323 std::vector<int32_t>& array) {
324 for (size_t i = 0; i < array.size(); i++) {
325 DoNotOptimize(array[i]);
326 }
327 }
328
JNI_Benchmark_SendByteArrayUseView(JNIEnv * env,ByteArrayView & array_view)329 static void JNI_Benchmark_SendByteArrayUseView(JNIEnv* env,
330 ByteArrayView& array_view) {
331 for (size_t i = 0; i < array_view.size(); i++) {
332 DoNotOptimize(array_view.data());
333 }
334 }
335
JNI_Benchmark_SendLargeObjectArray(JNIEnv * env,const JavaParamRef<jobjectArray> & j_array)336 static void JNI_Benchmark_SendLargeObjectArray(
337 JNIEnv* env,
338 const JavaParamRef<jobjectArray>& j_array) {
339 size_t array_size = static_cast<size_t>(env->GetArrayLength(j_array.obj()));
340 for (size_t i = 0; i < array_size; i++) {
341 DoNotOptimize(JNI_Integer::Java_Integer_intValue(
342 env, JavaParamRef(env, env->GetObjectArrayElement(j_array.obj(), i))));
343 }
344 }
345
JNI_Benchmark_SendLargeObjectList(JNIEnv * env,const JavaParamRef<jobject> & j_list)346 static void JNI_Benchmark_SendLargeObjectList(
347 JNIEnv* env,
348 const JavaParamRef<jobject>& j_list) {
349 size_t array_size = static_cast<size_t>(CollectionSize(env, j_list));
350 for (size_t i = 0; i < array_size; i++) {
351 DoNotOptimize(
352 JNI_Integer::Java_Integer_intValue(env, ListGet(env, j_list, i)));
353 }
354 }
355
JNI_Benchmark_SendSingleInt(JNIEnv * env,jint param)356 static void JNI_Benchmark_SendSingleInt(JNIEnv* env, jint param) {
357 DoNotOptimize(param);
358 }
359
JNI_Benchmark_SendSingleInteger(JNIEnv * env,const JavaParamRef<jobject> & param)360 static void JNI_Benchmark_SendSingleInteger(
361 JNIEnv* env,
362 const JavaParamRef<jobject>& param) {
363 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, param));
364 }
365
JNI_Benchmark_Send10Ints(JNIEnv * env,jint a,jint b,jint c,jint d,jint e,jint f,jint g,jint h,jint i,jint j)366 static void JNI_Benchmark_Send10Ints(JNIEnv* env,
367 jint a,
368 jint b,
369 jint c,
370 jint d,
371 jint e,
372 jint f,
373 jint g,
374 jint h,
375 jint i,
376 jint j) {
377 DoNotOptimize(a + b + c + d + e + f + g + h + i + j);
378 }
379
JNI_Benchmark_Send10Integers(JNIEnv * env,const JavaParamRef<jobject> & a,const JavaParamRef<jobject> & b,const JavaParamRef<jobject> & c,const JavaParamRef<jobject> & d,const JavaParamRef<jobject> & e,const JavaParamRef<jobject> & f,const JavaParamRef<jobject> & g,const JavaParamRef<jobject> & h,const JavaParamRef<jobject> & i,const JavaParamRef<jobject> & j)380 static void JNI_Benchmark_Send10Integers(JNIEnv* env,
381 const JavaParamRef<jobject>& a,
382 const JavaParamRef<jobject>& b,
383 const JavaParamRef<jobject>& c,
384 const JavaParamRef<jobject>& d,
385 const JavaParamRef<jobject>& e,
386 const JavaParamRef<jobject>& f,
387 const JavaParamRef<jobject>& g,
388 const JavaParamRef<jobject>& h,
389 const JavaParamRef<jobject>& i,
390 const JavaParamRef<jobject>& j) {
391 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, a));
392 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, b));
393 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, c));
394 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, d));
395 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, e));
396 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, f));
397 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, g));
398 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, h));
399 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, i));
400 DoNotOptimize(JNI_Integer::Java_Integer_intValue(env, j));
401 }
402
JNI_Benchmark_SendAsciiStringConvertedToU8(JNIEnv * env,std::string & param)403 static void JNI_Benchmark_SendAsciiStringConvertedToU8(JNIEnv* env,
404 std::string& param) {}
405
JNI_Benchmark_SendAsciiStringConvertedToU16(JNIEnv * env,std::u16string & param)406 static void JNI_Benchmark_SendAsciiStringConvertedToU16(JNIEnv* env,
407 std::u16string& param) {
408 }
409
JNI_Benchmark_SendNonAsciiStringConvertedToU8(JNIEnv * env,std::string & param)410 static void JNI_Benchmark_SendNonAsciiStringConvertedToU8(JNIEnv* env,
411 std::string& param) {}
412
JNI_Benchmark_SendNonAsciiStringConvertedToU16(JNIEnv * env,std::u16string & param)413 static void JNI_Benchmark_SendNonAsciiStringConvertedToU16(
414 JNIEnv* env,
415 std::u16string& param) {}
416
JNI_Benchmark_CallMe(JNIEnv * env)417 static void JNI_Benchmark_CallMe(JNIEnv* env) {}
418
JNI_Benchmark_SendListConverted(JNIEnv * env,std::vector<ScopedJavaLocalRef<jobject>> & vec)419 static void JNI_Benchmark_SendListConverted(
420 JNIEnv* env,
421 std::vector<ScopedJavaLocalRef<jobject>>& vec) {
422 for (size_t i = 0; i < vec.size(); i++) {
423 DoNotOptimize(vec[i].obj());
424 }
425 }
426
JNI_Benchmark_SendListObject(JNIEnv * env,const JavaParamRef<jobject> & j_list)427 static void JNI_Benchmark_SendListObject(JNIEnv* env,
428 const JavaParamRef<jobject>& j_list) {
429 int size = CollectionSize(env, j_list);
430 for (int i = 0; i < size; i++) {
431 DoNotOptimize(ListGet(env, j_list, i).obj());
432 }
433 }
434
435 } // namespace jni_zero::benchmark
436