Skip to content

JNI 性能优化

源:JNI Tips - Android Developers

JNI 调用存在固有开销,但通过正确的优化技巧,可以将性能提升数倍甚至数十倍。本文深入讲解 JNI 性能优化的方方面面。

JNI 性能概览

调用开销

每次 JNI 调用的开销主要来自:

开销来源耗时说明
方法查找~10ns首次调用需查找方法ID
参数编组 (Marshalling)可变Java→C 类型转换
栈帧切换~5nsJVM→Native 切换
返回值转换可变C→Java 类型转换

TIP

单次 JNI 调用的总开销约为 50-100ns(空调用),实际开销取决于参数复杂度。

性能指导原则

  1. 减少JNI 边界跨越 - 批量操作优于多次调用
  2. 避免数据拷贝 - 使用 DirectByteBuffer
  3. 缓存频繁使用的引用 - 类、方法、字段 ID
  4. 选择合适的数组访问方法 - 根据场景权衡

减少 JNI 调用次数

批量操作

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_Slow_processArray")
fun slowProcessArray(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    array: jintArray
) {
    val jniEnv = env.pointed.pointed!!
    val length = jniEnv.GetArrayLength!!(env, array)
    
    // ❌ 每个元素都调用一次 JNI
    for (i in 0 until length) {
        memScoped {
            val element = allocArray<jintVar>(1)
            jniEnv.GetIntArrayRegion!!(env, array, i, 1, element)
            
            // 处理单个元素
            val processed = element[0] * 2
            
            jniEnv.SetIntArrayRegion!!(env, array, i, 1, cValuesOf(processed).ptr)
        }
    }
}
// 性能:1000元素 ≈ 1ms
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_Fast_processArray")
fun fastProcessArray(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    array: jintArray
) {
    val jniEnv = env.pointed.pointed!!
    
    // ✅ 一次获取整个数组
    val elements = jniEnv.GetIntArrayElements!!(env, array, null)
    val length = jniEnv.GetArrayLength!!(env, array)
    
    // 批量处理
    for (i in 0 until length) {
        elements!![i] *= 2
    }
    
    // 一次性写回
    jniEnv.ReleaseIntArrayElements!!(env, array, elements, 0)
}
// 性能:1000元素 ≈ 0.05ms(20倍提升)

合并多个操作

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 多次调用
fun slowApproach(image: Bitmap) {
    applyGrayscale(image)      // JNI 调用1
    applyBlur(image, 5)        // JNI 调用2
    adjustBrightness(image, 1.2f)  // JNI 调用3
}

// ✅ 单次调用
@CName("Java_com_example_ImageProcessor_applyFilters")
fun applyFilters(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    pixels: jintArray,
    width: jint,
    height: jint,
    blurRadius: jint,
    brightness: jfloat
) {
    // 一次性应用所有滤镜
    // 灰度 → 模糊 → 亮度调整
}

DirectByteBuffer 零拷贝

使用 DirectByteBuffer

Direct存Buffer 直接操作堆外内存,避免数据拷贝:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_Direct_processDirect")
fun processDirect(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    buffer: jobject  // DirectByteBuffer
): jlong {
    val jniEnv = env.pointed.pointed!!
    
    // 获取 DirectByteBuffer 的 native 地址
    val addr = jniEnv.GetDirectBufferAddress!!(env, buffer)
    val capacity = jniEnv.GetDirectBufferCapacity!!(env, buffer)
    
    if (addr != null && capacity > 0) {
        val bytePtr = addr.reinterpret<ByteVar>()
        
        // ✅ 零拷贝:直接操作内存
        var checksum: Long = 0
        for (i in 0 until capacity) {
            checksum += bytePtr[i.toInt()].toUByte().toLong()
        }
        
        return checksum
    }
    
    return -1
}

性能对比

方法10MB 数据处理时间说明
ByteArray + GetByteArrayElements8ms需要拷贝
DirectByteBuffer0.5ms零拷贝

创建 DirectByteBuffer

kotlin
val buffer = ByteBuffer.allocateDirect(1024 * 1024)  // 1MB
nativeProcess(buffer)
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_Factory_createBuffer")
fun createBuffer(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    size: jlong
): jobject? {
    val jniEnv = env.pointed.pointed!!
    
    // 分配 native 内存
    val memory = nativeHeap.allocArray<ByteVar>(size.toInt())
    
    // 创建 DirectByteBuffer
    return jniEnv.NewDirectByteBuffer!!(env, memory, size)
    
    // 注意:内存生命周期需要手动管理
}

缓存策略

缓存类和方法 ID

频繁查找类和方法会严重影响性能:

kotlin
@OptIn(ExperimentalForeignApi::class)
object JNICache {
    // 缓存的类引用
    var stringClass: jclass? = null
    var integerClass: jclass? = null
    var arrayListClass: jclass? = null
    
    // 缓存的方法 ID
    var valueOfMethod: jmethodID? = null
    var addMethod: jmethodID? = null
    var toStringMethod: jmethodID? = null
    
    fun initialize(env: CPointer<JNIEnvVar>) {
        val jniEnv = env.pointed.pointed!!
        
        // 查找并缓存 String 类
        val localStringClass = jniEnv.FindClass!!(env, "java/lang/String")
        stringClass = jniEnv.NewGlobalRef!!(env, localStringClass) as jclass
        jniEnv.DeleteLocalRef!!(env, localStringClass)
        
        // 缓存 valueOf 方法
        val localIntClass = jniEnv.FindClass!!(env, "java/lang/Integer")
        integerClass = jniEnv.NewGlobalRef!!(env, localIntClass) as jclass
        valueOfMethod = jniEnv.GetStaticMethodID!!(
            env, integerClass, "valueOf", "(I)Ljava/lang/Integer;"
        )
        jniEnv.DeleteLocalRef!!(env, localIntClass)
        
        // 缓存 ArrayList.add
        val localListClass = jniEnv.FindClass!!(env, "java/util/ArrayList")
        arrayListClass = jniEnv.NewGlobalRef!!(env, localListClass) as jclass
        addMethod = jniEnv.GetMethodID!!(
            env, arrayListClass, "add", "(Ljava/lang/Object;)Z"
        )
        jniEnv.DeleteLocalRef!!(env, localListClass)
    }
    
    fun cleanup(env: CPointer<JNIEnvVar>) {
        val jniEnv = env.pointed.pointed!!
        
        stringClass?.let { jniEnv.DeleteGlobalRef!!(env, it) }
        integerClass?.let { jniEnv.DeleteGlobalRef!!(env, it) }
        arrayListClass?.let { jniEnv.DeleteGlobalRef!!(env, it) }
    }
}

// 使用缓存
@CName("Java_com_example_Example_useCached")
fun useCached(env: CPointer<JNIEnvVar>, thiz: jobject, value: jint) {
    val jniEnv = env.pointed.pointed!!
    
    // ✅ 直接使用缓存的 ID,无需每次查找
    val integer = jniEnv.CallStaticObjectMethod!!(
        env, 
        JNICache.integerClass, 
        JNICache.valueOfMethod, 
        value
    )
}

性能影响

操作未缓存已缓存提升
FindClass~500ns--
GetMethodID~300ns--
使用缓存的 ID-~50ns16x

数组访问优化

三种数组访问方法对比

kotlin
@OptIn(ExperimentalForeignApi::class)
// 方法1:GetIntArrayElements(推荐用于大数组)
fun method1_GetElements(env: CPointer<JNIEnvVar>, array: jintArray) {
    val jniEnv = env.pointed.pointed!!
    val isCopy = nativeHeap.alloc<jbooleanVar>()
    val elements = jniEnv.GetIntArrayElements!!(env, array, isCopy.ptr)
    val length = jniEnv.GetArrayLength!!(env, array)
    
    // 处理(可能是副本,也可能是直接访问)
    for (i in 0 until length) {
        elements!![i] *= 2
    }
    
    jniEnv.ReleaseIntArrayElements!!(env, array, elements, 0)
    nativeHeap.free(isCopy)
}

// 方法2:GetIntArrayRegion(推荐用于小数组)
fun method2_GetRegion(env: CPointer<JNIEnvVar>, array: jintArray) {
    val jniEnv = env.pointed.pointed!!
    val length = jniEnv.GetArrayLength!!(env, array)
    
    memScoped {
        val buffer = allocArray<jintVar>(length)
        
        // 拷贝到 native 缓冲区
        jniEnv.GetIntArrayRegion!!(env, array, 0, length, buffer)
        
        // 处理
        for (i in 0 until length) {
            buffer[i] *= 2
        }
        
        // 拷贝回去
        jniEnv.SetIntArrayRegion!!(env, array, 0, length, buffer)
    }
}

// 方法3:GetPrimitiveArrayCritical(最快,但有限制)
fun method3_GetCritical(env: CPointer<JNIEnvVar>, array: jintArray) {
    val jniEnv = env.pointed.pointed!!
    val length = jniEnv.GetArrayLength!!(env, array)
    val isCopy = nativeHeap.alloc<jbooleanVar>()
    
    // Critical 区域开始
    val elements = jniEnv.GetPrimitiveArrayCritical!!(env, array, isCopy.ptr)
        ?.reinterpret<IntVar>()
    
    // ⚠️ 在 Critical 区域内不能调用任何其他 JNI 函数
    // ⚠️ 也不能长时间阻塞
    for (i in 0 until length) {
        elements!![i] *= 2
    }
    
    // Critical 区域结束
    jniEnv.ReleasePrimitiveArrayCritical!!(env, array, elements, 0)
    nativeHeap.free(isCopy)
}

性能对比(1M 元素数组)

方法耗时优点缺点
GetIntArrayElements2.5ms灵活,可调用JNI可能拷贝
GetIntArrayRegion3.0ms明确拷贝,安全始终拷贝
GetPrimitiveArrayCritical0.8ms最快,可能零拷贝严格限制

选择建议

kotlin
@OptIn(ExperimentalForeignApi::class)
fun chooseMethod(array: jintArray, needJNICalls: Boolean, arraySize: Int) {
    when {
        // 小数组(<1000元素)- 使用 Region
        arraySize < 1000 -> method2_GetRegion(env, array)
        
        // 需要调用其他 JNI 函数 - 使用 Elements
        needJNICalls -> method1_GetElements(env, array)
        
        // 大数组 + 简单处理 - 使用 Critical
        else -> method3_GetCritical(env, array)
    }
}

字符串优化

字符串传递策略

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 频繁创建/销毁字符串
@CName("Java_com_example_Slow_processStrings")
fun slowProcessStrings(env: CPointer<JNIEnvVar>, thiz: jobject, count: jint): jobjectArray? {
    val jniEnv = env.pointed.pointed!!
    val stringClass = jniEnv.FindClass!!(env, "java/lang/String")
    val result = jniEnv.NewObjectArray!!(env, count, stringClass, null)
    
    for (i in 0 until count) {
        // 每次都创建新字符串
        val str = "Item $i".toJString(env)
        jniEnv.SetObjectArrayElement!!(env, result, i, str)
        jniEnv.DeleteLocalRef!!(env, str)  // 必须删除局部引用
    }
    
    return result
}

// ✅ 使用 StringBuilder 模式
@CName("Java_com_example_Fast_processStrings")
fun fastProcessStrings(env: CPointer<JNIEnvVar>, thiz: jobject, count: jint): jobjectArray? {
    val jniEnv = env.pointed.pointed!!
    
    // 在 native 侧构建所有字符串
    val strings = (0 until count).map { "Item $it" }
    
    // 一次性创建数组和字符串
    val stringClass = jniEnv.FindClass!!(env, "java/lang/String")
    val result = jniEnv.NewObjectArray!!(env, count, stringClass, null)
    
    strings.forEachIndexed { i, str ->
        val jstr = str.toJString(env)
        jniEnv.SetObjectArrayElement!!(env, result, i, jstr)
        jniEnv.DeleteLocalRef!!(env, jstr)
    }
    
    return result
}

内存管理优化

避免局部引用泄漏

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 局部引用泄漏
@CName("Java_com_example_Bad_leakReferences")
fun leakReferences(env: CPointer<JNIEnvVar>, thiz: jobject, array: jobjectArray) {
    val jniEnv = env.pointed.pointed!!
    val length = jniEnv.GetArrayLength!!(env, array)
    
    for (i in 0 until length) {
        val element = jniEnv.GetObjectArrayElement!!(env, array, i)
        // ❌ 未删除局部引用,循环中积累大量引用
        // 处理 element...
    }
    // 可能耗尽局部引用表
}

// ✅ 正确管理局部引用
@CName("Java_com_example_Good_manageReferences")
fun manageReferences(env: CPointer<JNIEnvVar>, thiz: jobject, array: jobjectArray) {
    val jniEnv = env.pointed.pointed!!
    val length = jniEnv.GetArrayLength!!(env, array)
    
    for (i in 0 until length) {
        val element = jniEnv.GetObjectArrayElement!!(env, array, i)
        
        // 处理 element...
        
        // ✅ 立即删除局部引用
        jniEnv.DeleteLocalRef!!(env, element)
    }
}

性能测试工具

基准测试框架

kotlin
@OptIn(ExperimentalForeignApi::class)
object JNIBenchmark {
    fun measure(name: String, iterations: Int, block: () -> Unit): Long {
        // 预热
        repeat(100) { block() }
        
        // 测量
        val start = kotlin.system.getTimeNanos()
        repeat(iterations) { block() }
        val end = kotlin.system.getTimeNanos()
        
        val avgNs = (end - start) / iterations
        println("$name: ${avgNs}ns per iteration")
        
        return avgNs
    }
}

// 使用
fun benchmarkArrayAccess() {
    val array = IntArray(10000) { it }
    
    JNIBenchmark.measure("GetIntArrayElements", 1000) {
        nativeProcessWithElements(array)
    }
    
    JNIBenchmark.measure("GetIntArrayRegion", 1000) {
        nativeProcessWithRegion(array)
    }
    
    JNIBenchmark.measure("DirectByteBuffer", 1000) {
        val buffer = ByteBuffer.allocateDirect(10000 * 4)
        nativeProcessDirect(buffer)
    }
}

JNI 性能优化的核心是减少边界跨越、避免数据拷贝、合理缓存。遵循这些原则,性能提升10-100倍是常见的。