CriticalNative and FastNative in tracing-perfetto
Using CriticalNative and FastNative to speed up performance of critical
tracing-perfetto JNI calls.
Additionally, explicitly registering JNI methods with JVM instead of
relying on JNIEXPORT and method name matching.
Bug: 215352263
Test: TrivialTracingBenchmark, PerfettoSdkOverheadBenchmark
Change-Id: I5f088965ed3750f4ba884c04b9dbbe137432faa7
diff --git a/tracing/tracing-perfetto-binary/build.gradle b/tracing/tracing-perfetto-binary/build.gradle
index 11f4655..041c702 100644
--- a/tracing/tracing-perfetto-binary/build.gradle
+++ b/tracing/tracing-perfetto-binary/build.gradle
@@ -78,6 +78,7 @@
androidTestImplementation(libs.kotlinStdlib)
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testRunner)
+ androidTestImplementation(project(":benchmark:benchmark-common")) // for supported ABI checks
}
androidx {
diff --git a/tracing/tracing-perfetto-binary/src/androidTest/java/androidx/tracing/perfetto/binary/test/TracingTest.kt b/tracing/tracing-perfetto-binary/src/androidTest/java/androidx/tracing/perfetto/binary/test/TracingTest.kt
index 520ff8d..4b4e2b2 100644
--- a/tracing/tracing-perfetto-binary/src/androidTest/java/androidx/tracing/perfetto/binary/test/TracingTest.kt
+++ b/tracing/tracing-perfetto-binary/src/androidTest/java/androidx/tracing/perfetto/binary/test/TracingTest.kt
@@ -18,14 +18,41 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import dalvik.system.BaseDexClassLoader
+import java.io.File
+import java.util.zip.ZipFile
+import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class TracingTest {
+ /**
+ * The test verifies that the library was assembled and can be found by the system.
+ * We cannot load the library since it contains explicit JNI method registration tied to
+ * the [androidx.tracing.perfetto.jni.PerfettoNative] class.
+ *
+ * Methods of the library are further tested in e.g.:
+ * - [androidx.tracing.perfetto.jni.test.PerfettoNativeTest]
+ * - [androidx.compose.integration.macrobenchmark.TrivialTracingBenchmark]
+ */
@Test
- fun test_loadLibrary() {
- System.loadLibrary("tracing_perfetto")
+ fun test_library_was_created() {
+ // check that the system can resolve the library
+ val nativeLibraryName = System.mapLibraryName("tracing_perfetto")
+
+ // check that the class loader can find the library
+ val classLoader = javaClass.classLoader as BaseDexClassLoader
+ val libraryPath = classLoader.findLibrary("tracing_perfetto")
+ assertTrue(libraryPath.endsWith("/$nativeLibraryName"))
+
+ // check that the APK contains the library file
+ val context = InstrumentationRegistry.getInstrumentation().context
+ val baseApk = File(context.applicationInfo.publicSourceDir!!)
+ assertTrue(ZipFile(baseApk).entries().asSequence().any {
+ it.name.endsWith("/$nativeLibraryName")
+ })
}
}
diff --git a/tracing/tracing-perfetto-binary/src/main/cpp/jni/androidx_tracing_perfetto_jni_PerfettoNative.cc b/tracing/tracing-perfetto-binary/src/main/cpp/jni/androidx_tracing_perfetto_jni_PerfettoNative.cc
index b0c2953..faffb2f 100644
--- a/tracing/tracing-perfetto-binary/src/main/cpp/jni/androidx_tracing_perfetto_jni_PerfettoNative.cc
+++ b/tracing/tracing-perfetto-binary/src/main/cpp/jni/androidx_tracing_perfetto_jni_PerfettoNative.cc
@@ -20,13 +20,13 @@
extern "C" {
-JNIEXPORT void JNICALL
+static void JNICALL
Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeRegisterWithPerfetto(
JNIEnv *env, __unused jclass clazz) {
tracing_perfetto::RegisterWithPerfetto();
}
-JNIEXPORT void JNICALL
+static void JNICALL
Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeTraceEventBegin(
JNIEnv *env, __unused jclass clazz, jint key, jstring traceInfo) {
const char *traceInfoUtf = env->GetStringUTFChars(traceInfo, NULL);
@@ -34,16 +34,68 @@
env->ReleaseStringUTFChars(traceInfo, traceInfoUtf);
}
-JNIEXPORT void JNICALL
-Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeTraceEventEnd(
- JNIEnv *env, __unused jclass clazz) {
+static void JNICALL
+Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeTraceEventEnd() {
tracing_perfetto::TraceEventEnd();
}
-JNIEXPORT jstring JNICALL
+static jstring JNICALL
Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeVersion(
JNIEnv *env, __unused jclass clazz) {
return env->NewStringUTF(tracing_perfetto::Version());
}
} // extern "C"
+// Explicitly registering native methods using CriticalNative / FastNative as per:
+// https://source.android.com/devices/tech/dalvik/improvements#faster-native-methods.
+// Note: this applies to Android 8 - 11. In Android 12+, this is recommended (to avoid slow lookups
+// on first use), but not necessary.
+
+static JNINativeMethod sMethods[] = {
+ {"nativeRegisterWithPerfetto",
+ "()V",
+ reinterpret_cast<void *>(
+ Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeRegisterWithPerfetto)
+ },
+ {"nativeTraceEventBegin",
+ "(ILjava/lang/String;)V",
+ reinterpret_cast<void *>(
+ Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeTraceEventBegin)
+ },
+ {"nativeTraceEventEnd",
+ "()V",
+ reinterpret_cast<void *>(
+ Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeTraceEventEnd)
+ },
+ {"nativeVersion",
+ "()Ljava/lang/String;",
+ reinterpret_cast<void *>(
+ Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeVersion)
+ },
+};
+
+jint JNI_OnLoad(JavaVM *vm, void * /* reserved */) {
+ JNIEnv *env = NULL;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) {
+ PERFETTO_LOG("JNI_OnLoad failure when trying to register native methods for tracing.");
+ return JNI_ERR;
+ }
+ jclass clazz = env->FindClass("androidx/tracing/perfetto/jni/PerfettoNative");
+ if (clazz == NULL) {
+ PERFETTO_LOG("Cannot find PerfettoNative class when trying to register native methods for "
+ "tracing.");
+ return JNI_ERR;
+ }
+
+ int result = env->RegisterNatives(clazz, sMethods, 4);
+ env->DeleteLocalRef(clazz);
+
+ if (result != 0) {
+ PERFETTO_LOG("Failure when trying to call RegisterNatives to register native methods for "
+ "tracing.");
+ return JNI_ERR;
+ }
+
+ PERFETTO_LOG("Successfully registered native methods for tracing.");
+ return JNI_VERSION_1_6;
+}
\ No newline at end of file
diff --git a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
index 419e348..00e6c5a 100644
--- a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
+++ b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
@@ -16,6 +16,8 @@
package androidx.tracing.perfetto.jni
import androidx.tracing.perfetto.security.SafeLibLoader
+import dalvik.annotation.optimization.CriticalNative
+import dalvik.annotation.optimization.FastNative
import java.io.File
internal object PerfettoNative {
@@ -25,10 +27,10 @@
object Metadata {
const val version = "1.0.0-alpha07"
val checksums = mapOf(
- "arm64-v8a" to "abb5fe78f243999cab6fc8ea05e6d0743df3582e7ea4782f5ef2a677d8a0bde7",
- "armeabi-v7a" to "d40c8487f7bca0917dcd6c0626d179394ef4b28d6a2fed809bc34e8ea15a100e",
- "x86" to "38e8e5872e2916b993ca5d37894d7973934730bc427af20de4a52f0656a9ce0f",
- "x86_64" to "ef545e11ceb45bf0eb611d12867bf9b9c5c9361d7e1865f8723a7505d3107ab8",
+ "arm64-v8a" to "3d9fa02a04459e3480f28ae7f3a8ced30f181f02a0e6bc6caf85704faea84857",
+ "armeabi-v7a" to "9eb7bf76a94fce79e682204c9e702c872d15c85f2ebd48de61aa541e7e6de034",
+ "x86" to "6eb72683bf58eeb0175f094d88bc0e00744b1a593dc7eaac9ee15168b1ab9234",
+ "x86_64" to "eabf725464c24c9709d8e2ea073233e5712949bb65aac5c4972528d813b55c74",
)
}
@@ -36,8 +38,17 @@
fun loadLib(file: File, loader: SafeLibLoader) = loader.loadLib(file, Metadata.checksums)
+ @JvmStatic
external fun nativeRegisterWithPerfetto()
+
+ @FastNative
+ @JvmStatic
external fun nativeTraceEventBegin(key: Int, traceInfo: String)
+
+ @CriticalNative
+ @JvmStatic
external fun nativeTraceEventEnd()
+
+ @JvmStatic
external fun nativeVersion(): String
}
diff --git a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/CriticalNative.java b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/CriticalNative.java
new file mode 100644
index 0000000..13fbfd2
--- /dev/null
+++ b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/CriticalNative.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.annotation.optimization;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Applied to native methods to enable an ART runtime built-in optimization:
+ * methods that are annotated this way can speed up JNI transitions for methods that contain no
+ * objects (in parameters or return values, or as an implicit {@code this}).
+ *
+ * <p>
+ * The native implementation must exclude the {@code JNIEnv} and {@code jclass} parameters from its
+ * function signature. As an additional limitation, the method must be explicitly registered with
+ * {@code RegisterNatives} instead of relying on the built-in dynamic JNI linking.
+ * </p>
+ *
+ * <p>
+ * Performance of JNI transitions:
+ * <ul>
+ * <li>Regular JNI cost in nanoseconds: 115
+ * <li>Fast {@code (!)} JNI cost in nanoseconds: 60
+ * <li>{@literal @}{@link FastNative} cost in nanoseconds: 35
+ * <li>{@literal @}{@code CriticalNative} cost in nanoseconds: 25
+ * </ul>
+ * (Measured on angler-userdebug in 07/2016).
+ * </p>
+ *
+ * <p>
+ * A similar annotation, {@literal @}{@link FastNative}, exists with similar performance guarantees.
+ * However, unlike {@code @CriticalNative} it supports non-statics, object return values, and object
+ * parameters. If a method absolutely must have access to a {@code jobject}, then use
+ * {@literal @}{@link FastNative} instead of this.
+ * </p>
+ *
+ * <p>
+ * This has the side-effect of disabling all garbage collections while executing a critical native
+ * method. Use with extreme caution. Any long-running methods must not be marked with
+ * {@code @CriticalNative} (including usually-fast but generally unbounded methods)!
+ * </p>
+ *
+ * <p>
+ * <b>Deadlock Warning:</b> As a rule of thumb, do not acquire any locks during a critical native
+ * call if they aren't also locally released [before returning to managed code].
+ * </p>
+ *
+ * <p>
+ * Say some code does:
+ *
+ * <code>
+ * critical_native_call_to_grab_a_lock();
+ * does_some_java_work();
+ * critical_native_call_to_release_a_lock();
+ * </code>
+ *
+ * <p>
+ * This code can lead to deadlocks. Say thread 1 just finishes
+ * {@code critical_native_call_to_grab_a_lock()} and is in {@code does_some_java_work()}.
+ * GC kicks in and suspends thread 1. Thread 2 now is in
+ * {@code critical_native_call_to_grab_a_lock()} but is blocked on grabbing the
+ * native lock since it's held by thread 1. Now thread suspension can't finish
+ * since thread 2 can't be suspended since it's doing CriticalNative JNI.
+ * </p>
+ *
+ * <p>
+ * Normal natives don't have the issue since once it's executing in native code,
+ * it is considered suspended from the runtime's point of view.
+ * CriticalNative natives however don't do the state transition done by the normal natives.
+ * </p>
+ *
+ * <p>
+ * This annotation has no effect when used with non-native methods.
+ * The runtime must throw a {@code VerifierError} upon class loading if this is used with a native
+ * method that contains object parameters, an object return value, or a non-static.
+ * </p>
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@Retention(RetentionPolicy.CLASS) // Save memory, don't instantiate as an object at runtime.
+@Target(ElementType.METHOD)
+public @interface CriticalNative {}
+
diff --git a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/FastNative.java b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/FastNative.java
new file mode 100644
index 0000000..3eb3745
--- /dev/null
+++ b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/FastNative.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.annotation.optimization;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An ART runtime built-in optimization for "native" methods to speed up JNI transitions.
+ *
+ * <p>
+ * This has the side-effect of disabling all garbage collections while executing a fast native
+ * method. Use with extreme caution. Any long-running methods must not be marked with
+ * {@code @FastNative} (including usually-fast but generally unbounded methods)!</p>
+ *
+ * <p><b>Deadlock Warning:</b>As a rule of thumb, do not acquire any locks during a fast native
+ * call if they aren't also locally released [before returning to managed code].</p>
+ *
+ * <p>
+ * Say some code does:
+ *
+ * <code>
+ * fast_jni_call_to_grab_a_lock();
+ * does_some_java_work();
+ * fast_jni_call_to_release_a_lock();
+ * </code>
+ *
+ * <p>
+ * This code can lead to deadlocks. Say thread 1 just finishes
+ * {@code fast_jni_call_to_grab_a_lock()} and is in {@code does_some_java_work()}.
+ * GC kicks in and suspends thread 1. Thread 2 now is in {@code fast_jni_call_to_grab_a_lock()}
+ * but is blocked on grabbing the native lock since it's held by thread 1.
+ * Now thread suspension can't finish since thread 2 can't be suspended since it's doing
+ * FastNative JNI.
+ * </p>
+ *
+ * <p>
+ * Normal JNI doesn't have the issue since once it's in native code,
+ * it is considered suspended from java's point of view.
+ * FastNative JNI however doesn't do the state transition done by JNI.
+ * </p>
+ *
+ * <p>
+ * Note that even in FastNative methods you <b>are</b> allowed to
+ * allocate objects and make upcalls into Java code. A call from Java to
+ * a FastNative function and back to Java is equivalent to a call from one Java
+ * method to another. What's forbidden in a FastNative method is blocking
+ * the calling thread in some non-Java code and thereby preventing the thread
+ * from responding to requests from the garbage collector to enter the suspended
+ * state.
+ * </p>
+ *
+ * <p>
+ * Has no effect when used with non-native methods.
+ * </p>
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@Retention(RetentionPolicy.CLASS) // Save memory, don't instantiate as an object at runtime.
+@Target(ElementType.METHOD)
+public @interface FastNative {}
+
diff --git a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/README.md b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/README.md
new file mode 100644
index 0000000..e604041
--- /dev/null
+++ b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/README.md
@@ -0,0 +1,9 @@
+# Rationale
+
+`FastNative` and `CriticalNative` annotations are duplicated from the platform, so that the platform
+detects their presence, and applies them, even though they're not part of platform public API.
+
+# Next steps
+
+Once the annotations become public in the platform, remove duplicate files and use the platform.
+Tracking item: b/35664282