Optimize BpfMap JNI calls with FastNative/CriticalNative annotations

Convert BpfMap native methods to use @FastNative to reduce JNI overhead.

Refactor synchronizeKernelRCU() to be @CriticalNative for maximum
performance by changing its error handling to return an error code
instead of throwing directly from native code.
(In practice we don't expect it to *ever* fail)

FYI:

@FastNative and @CriticalNative are Android-specific annotations
that signal to the ART runtime to use a lower-overhead JNI transition,
resulting in better performance.

A standard JNI call tells the Garbage Collector (GC) that the thread
is transitioning to a 'Native' state. This is a signal to the GC that
the thread might be busy for a while and shouldn't be suspended arbitrarily.

@FastNative is for short, quick functions:
  This provides a moderately faster JNI call (thread is still 'Runnable').
  The native function signature remains the same as a standard JNI method,
  and it can still access the JNIEnv* to interact with Java objects
  or throw exceptions.

@CriticalNative is for short, quick & trivial functions:
  This provides the fastest possible JNI transition and is intended
  for simple, performance-critical leaf functions.
  It comes with strict requirements:
    - The native method cannot throw exceptions, create Java objects,
      or make any other JNI calls.
    - All arguments and the return type must be primitive, ie:
        jboolean [bool]
        jbyte [s8]
        jchar [u16]
        jshort [s16]
        jint [s32]
        jlong [s64]
        jfloat [32-bit]
        jdouble [64-bit]
        void
      Java objects like String or byte[] are not allowed.
  Because of these restrictions, the native C++ function signature
  must not include JNIEnv* or jobject/jclass parameters.
  (This likely also means it must be 'static')

Test: TreeHugger
Flag: EXEMPT mainline
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: I9b6107b26086df2e87218ff9274593e9f2eb6b0b
This commit is contained in:
Maciej Żenczykowski 2025-08-12 13:28:10 -07:00
commit 2afef5070a
2 changed files with 22 additions and 16 deletions

View file

@ -28,6 +28,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@ -262,9 +265,14 @@ public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V>
/** Synchronize Kernel RCU */
public static void synchronizeKernelRCU() throws ErrnoException {
nativeSynchronizeKernelRCU();
final int err = nativeSynchronizeKernelRCU();
if (err != 0) {
final int errno = -err;
throw new ErrnoException("nativeSynchronizeKernelRCU", errno);
}
}
@FastNative
private static native int nativeBpfFdGet(String path, int mode, int keySize, int valueSize)
throws ErrnoException, NullPointerException;
@ -273,19 +281,24 @@ public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V>
// the object from being garbage collected (and thus potentially maps closed) prior
// to the native code actually running (with a possibly already closed fd).
@FastNative
private native void nativeWriteToMapEntry(int fd, byte[] key, byte[] value, int flags)
throws ErrnoException;
@FastNative
private native boolean nativeDeleteMapEntry(int fd, byte[] key) throws ErrnoException;
// If key is found, the operation returns true and the nextKey would reference to the next
// element. If key is not found, the operation returns true and the nextKey would reference to
// the first element. If key is the last element, false is returned.
@FastNative
private native boolean nativeGetNextMapKey(int fd, byte[] key, byte[] nextKey)
throws ErrnoException;
@FastNative
private native boolean nativeFindMapEntry(int fd, byte[] key, byte[] value)
throws ErrnoException;
private static native void nativeSynchronizeKernelRCU() throws ErrnoException;
@CriticalNative
private static native int nativeSynchronizeKernelRCU();
}

View file

@ -135,20 +135,12 @@ static jboolean com_android_net_module_util_BpfMap_nativeFindMapEntry(JNIEnv *en
return throwIfNotEnoent(env, "nativeFindMapEntry", ret, errno);
}
static void com_android_net_module_util_BpfMap_nativeSynchronizeKernelRCU(JNIEnv *env,
jclass clazz) {
static jint com_android_net_module_util_BpfMap_nativeSynchronizeKernelRCU() {
const int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2);
if (pfSocket < 0) {
jniThrowErrnoException(env, "nativeSynchronizeKernelRCU:socket", errno);
return;
}
if (close(pfSocket)) {
jniThrowErrnoException(env, "nativeSynchronizeKernelRCU:close", errno);
return;
}
return;
if (pfSocket < 0) return -errno;
// On Linux close() will always close the fd, any error it returns is a previous pending error.
if (close(pfSocket)) return -errno; // in practice cannot fail
return 0;
}
/*
@ -166,7 +158,8 @@ static const JNINativeMethod gMethods[] = {
(void*) com_android_net_module_util_BpfMap_nativeGetNextMapKey },
{ "nativeFindMapEntry", "(I[B[B)Z",
(void*) com_android_net_module_util_BpfMap_nativeFindMapEntry },
{ "nativeSynchronizeKernelRCU", "()V",
// CriticalNative
{ "nativeSynchronizeKernelRCU", "()I",
(void*) com_android_net_module_util_BpfMap_nativeSynchronizeKernelRCU },
};