diff --git a/android/core/core/build.gradle b/android/core/core/build.gradle index 3f71a01..083e5c8 100644 --- a/android/core/core/build.gradle +++ b/android/core/core/build.gradle @@ -25,6 +25,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + + kotlinOptions { + jvmTarget = '1.8' + } } preBuild.doFirst { diff --git a/android/core/core/src/androidTest/java/org/coepi/core/JniBenchmarks.kt b/android/core/core/src/androidTest/java/org/coepi/core/JniBenchmarks.kt new file mode 100644 index 0000000..ae0e47b --- /dev/null +++ b/android/core/core/src/androidTest/java/org/coepi/core/JniBenchmarks.kt @@ -0,0 +1,158 @@ +package org.coepi.core + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.coepi.core.jni.BenchmarksIntClass +import org.coepi.core.jni.JniApi +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ + +// NOTE: Ideally this should use benchmark runner +// https://developer.android.com/studio/profile/build-benchmarks-without-gradle +// The documentation is incomplete, though, and it was consuming too much time +// For now "manual" benchmarks + +@Ignore("Benchmarks") +@ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) +class JniBenchmarks { + + private val jniApi = JniApi() + private val nonJniApi = NonJniApi() + + @Test + fun benchmarkNoopWithJni() { + // 70ms, 97ms, 80ms, 85ms + benchmark("benchmarkNoop") { + for (i in 0..1000000) { + jniApi.noopForBenchmarks() + } + } + } + + @Test + fun benchmarkNoopWithoutJni() { + // 22ms, 22ms, 21ms, 22ms + benchmark("benchmarkNoopWithoutJni") { + for (i in 0..1000000) { + nonJniApi.noopForBenchmarks() + } + } + } + + @Test + fun benchmarkSendReceiveIntWithJni() { + // 72ms, 79ms, 89ms, 90ms + benchmark("benchmarkSendReceiveIntWithJni") { + for (i in 0..1000000) { + jniApi.sendReceiveIntForBenchmarks(1) + } + } + } + + @Test + fun benchmarkSendReceiveIntWithoutJni() { + // 22ms, 10ms, 23ms, 23ms + benchmark("benchmarkSendReceiveIntWithoutJni") { + for (i in 0..1000000) { + nonJniApi.sendReceiveIntForBenchmarks(1) + } + } + } + + @Test + fun benchmarkSendReceiveStringWithJni() { + // 4596ms, 4343ms, 4561ms, 4360ms + benchmark("benchmarkSendReceiveStringWithJni") { + for (i in 0..1000000) { + jniApi.sendCreateStringForBenchmarks("hello") + } + } + } + + @Test + fun benchmarkSendReceiveStringDontUseInputWithJni() { + // 2415ms, 2439ms, 2415ms, 2455ms + benchmark("sendCreateStringDontUseInputForBenchmarks") { + for (i in 0..1000000) { + jniApi.sendCreateStringDontUseInputForBenchmarks("hello") + } + } + } + + @Test + fun benchmarkSendReceiveStringWithoutJni() { + // 12ms, 31ms, 27ms, 27ms + benchmark("benchmarkSendReceiveStringWithoutJni") { + for (i in 0..1000000) { + nonJniApi.sendCreateStringForBenchmarks("hello") + } + } + } + + @Test + fun benchmarkSendClassWithJni() { + // 8639ms, 8690ms, 8476ms, 8694ms + benchmark("benchmarkClassStructWithJni") { + for (i in 0..1000000) { + jniApi.sendClassForBenchmarks(BenchmarksIntClass(1)) + } + } + } + + @Test + fun benchmarkSendClassWithoutJni() { + // 39ms, 31ms, 32ms, 41ms + benchmark("benchmarkClassStructWithoutJni") { + for (i in 0..1000000) { + nonJniApi.sendClassForBenchmarks(BenchmarksIntClass(1)) + } + } + } + + @Test + fun benchmarkReturnClassWithJni() { + // 37929ms, 39027ms, 38815ms, 38711ms + benchmark("benchmarkReturnClassWithJni") { + for (i in 0..1000000) { + jniApi.returnClassForBenchmarks() + } + } + } + + @Test + fun benchmarkReturnClassWithoutJni() { + // 16ms, 40ms, 41ms, 30ms + benchmark("benchmarkReturnClassWithoutJni") { + for (i in 0..1000000) { + nonJniApi.returnClassForBenchmarks() + } + } + } + + private fun benchmark(label: String, f: () -> Unit) { + val tsLong = System.currentTimeMillis() + f() + val ttLong = System.currentTimeMillis() - tsLong + println("$label took: ${ttLong}ms") + } +} + +private class NonJniApi { + fun noopForBenchmarks() {} + + fun sendReceiveIntForBenchmarks(i: Int): Int = 1 + + fun sendCreateStringForBenchmarks(string: String): String = "Return string" + + fun sendClassForBenchmarks(c: BenchmarksIntClass) {} + + fun returnClassForBenchmarks() = BenchmarksIntClass(1) +} diff --git a/android/core/core/src/main/java/org/coepi/core/jni/JniApi.kt b/android/core/core/src/main/java/org/coepi/core/jni/JniApi.kt index 4e7b35c..7b94bc8 100644 --- a/android/core/core/src/main/java/org/coepi/core/jni/JniApi.kt +++ b/android/core/core/src/main/java/org/coepi/core/jni/JniApi.kt @@ -68,9 +68,28 @@ class JniApi { external fun testReturnMultipleAlerts(): JniAlertsArrayResult + // Benchmarks + + external fun noopForBenchmarks() + + external fun sendReceiveIntForBenchmarks(i: Int): Int + + external fun sendClassForBenchmarks(c: BenchmarksIntClass): Int + + external fun returnClassForBenchmarks(): BenchmarksIntClass + + external fun sendCreateStringForBenchmarks(string: String): String + + // Doesn't do anything with the input string + external fun sendCreateStringDontUseInputForBenchmarks(string: String): String + ///////////////////////////////////////////////////////////////////////////////// } +data class BenchmarksIntClass( + val myInt: Int +) + data class FFIParameterStruct( val myInt: Int, val myStr: String, diff --git a/src/android/ffi_benchmarks.rs b/src/android/ffi_benchmarks.rs new file mode 100644 index 0000000..0f2aa80 --- /dev/null +++ b/src/android/ffi_benchmarks.rs @@ -0,0 +1,68 @@ +extern crate jni; +use self::jni::JNIEnv; +use jni::objects::{JClass, JObject, JString, JValue}; +use jni::sys::{jint, jobject, jstring}; + +#[no_mangle] +pub unsafe extern "C" fn Java_org_coepi_core_jni_JniApi_noopForBenchmarks(env: JNIEnv, _: JClass) {} + +#[no_mangle] +pub unsafe extern "C" fn Java_org_coepi_core_jni_JniApi_sendReceiveIntForBenchmarks( + env: JNIEnv, + _: JClass, + i: jint, +) -> jint { + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn Java_org_coepi_core_jni_JniApi_sendCreateStringForBenchmarks( + env: JNIEnv, + _: JClass, + string: JString, +) -> jstring { + let string: String = env + .get_string(string) + .expect("Couldn't create java string") + .into(); + + let output = env + .new_string("Return string") + .expect("Couldn't create java string"); + + output.into_inner() +} + +#[no_mangle] +pub unsafe extern "C" fn Java_org_coepi_core_jni_JniApi_sendCreateStringDontUseInputForBenchmarks( + env: JNIEnv, + _: JClass, + string: JString, +) -> jstring { + let output = env + .new_string("Return string") + .expect("Couldn't create java string"); + + output.into_inner() +} + +#[no_mangle] +pub unsafe extern "C" fn Java_org_coepi_core_jni_JniApi_sendClassForBenchmarks( + env: JNIEnv, + _: JClass, + my_struct: JObject, +) { + let my_int_j_value_res = env.get_field(my_struct, "myInt", "I"); + let my_int: i32 = my_int_j_value_res.unwrap().i().unwrap(); +} + +#[no_mangle] +pub unsafe extern "C" fn Java_org_coepi_core_jni_JniApi_returnClassForBenchmarks( + env: JNIEnv, + _: JClass, +) -> jobject { + let cls = env.find_class("org/coepi/core/jni/BenchmarksIntClass"); + let my_int_j_value = JValue::from(123); + let obj = env.new_object(cls.unwrap(), "(I)V", &[my_int_j_value]); + obj.unwrap().into_inner() +} diff --git a/src/android/mod.rs b/src/android/mod.rs index f05c3e9..768d3a6 100644 --- a/src/android/mod.rs +++ b/src/android/mod.rs @@ -1,3 +1,4 @@ pub mod android_interface; +pub mod ffi_benchmarks; pub mod ffi_for_sanity_tests; pub mod jni_domain_tests;