Android 转换为C/NDK/JNI的代码的效率低于Java原始版本

Android 转换为C/NDK/JNI的代码的效率低于Java原始版本,android,c,performance,android-ndk,java-native-interface,Android,C,Performance,Android Ndk,Java Native Interface,这是我第一次深入NDK 为了提高性能,我想重写NDK。我的c文件如下所示: #include <jni.h> #include <stdbool.h> #include <stdio.h> #include <time.h> #include <android/log.h> JNIEXPORT jbyteArray JNICALL Java_com_company_app_tools_NV21FrameRotator_rotateNV

这是我第一次深入NDK

为了提高性能,我想重写NDK。我的
c
文件如下所示:

#include <jni.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
#include <android/log.h>

JNIEXPORT jbyteArray JNICALL
Java_com_company_app_tools_NV21FrameRotator_rotateNV21(JNIEnv *env, jclass thiz,
                                                           jbyteArray data, jbyteArray output,
                                                           jint width, jint height, jint rotation) {
    clock_t start, end;
    double cpu_time_used;
    start = clock();

    jbyte *dataPtr = (*env)->GetByteArrayElements(env, data, NULL);
    jbyte *outputPtr = (*env)->GetByteArrayElements(env, output, NULL);

    unsigned int frameSize = width * height;
    bool swap = rotation % 180 != 0;
    bool xflip = rotation % 270 != 0;
    bool yflip = rotation >= 180;

    for (unsigned int j = 0; j < height; j++) {
        for (unsigned int i = 0; i < width; i++) {
            unsigned int yIn = j * width + i;
            unsigned int uIn = frameSize + (j >> 1u) * width + (i & ~1u);
            unsigned int vIn = uIn + 1;

            unsigned int wOut = swap ? height : width;
            unsigned int hOut = swap ? width : height;
            unsigned int iSwapped = swap ? j : i;
            unsigned int jSwapped = swap ? i : j;
            unsigned int iOut = xflip ? wOut - iSwapped - 1 : iSwapped;
            unsigned int jOut = yflip ? hOut - jSwapped - 1 : jSwapped;

            unsigned int yOut = jOut * wOut + iOut;
            unsigned int uOut = frameSize + (jOut >> 1u) * wOut + (iOut & ~1u);
            unsigned int vOut = uOut + 1;

            outputPtr[yOut] = (jbyte) (0xff & dataPtr[yIn]);
            outputPtr[uOut] = (jbyte) (0xff & dataPtr[uIn]);
            outputPtr[vOut] = (jbyte) (0xff & dataPtr[vIn]);
        }
    }

    (*env)->ReleaseByteArrayElements(env, data, dataPtr, 0);
    (*env)->ReleaseByteArrayElements(env, output, outputPtr, 0);

    end = clock();
    cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;

    char str[10];
    sprintf(str, "%f", cpu_time_used * 1000);
    __android_log_write(ANDROID_LOG_ERROR, "NV21FrameRotator", str);

    return output;
}
PS.Java日志有时显示的持续时间比
c
文件中的本机
clock()
测量的持续时间短。。。示例日志:

NV21FrameRotator: 7.942000
NV21RotatorJava: Last frame processing duration: 7403µs
NV21FrameRotator: 7.229000
NV21RotatorJava: Last frame processing duration: 7166µs
NV21FrameRotator: 16.918000
NV21RotatorJava: Last frame processing duration: 20644µs
NV21FrameRotator: 19.594000
NV21RotatorJava: Last frame processing duration: 20479µs
NV21FrameRotator: 9.484000
NV21RotatorJava: Last frame processing duration: 7274µs
编辑:
compile\u commands.json
for
armeabi-v7a
(旧设备,我只构建这个)

cmakfile

cmake_minimum_required(VERSION 3.4.1)
add_library(NV21FrameRotator SHARED
    NV21FrameRotator.c)
find_library(log-lib
    log )
target_link_libraries(NV21FrameRotator
    ${log-lib} )

JNI的开销非常高,尤其是在传递非POD类型或缓冲区时。因此,通常调用JNI函数可能比java版本慢得多


考虑传递java.nio.ByteBuffer,以避免字节数组的潜在副本。

JNI的开销非常大,尤其是在传递非POD类型或缓冲区时。因此,通常调用JNI函数可能比java版本慢得多

考虑传递java.nio.ByteBuffer,以避免字节数组的潜在副本

  • 在实际设备上比较Java和C的性能,仿真器将不会产生可靠的结果

  • 比较Java和C在版本构建时的性能,C中的调试速度很慢,而Java仍然得到完全的JIT(和AOT)优化

  • 您可以为您的场景寻找最佳优化选择。默认情况下,版本将使用
    -Oz
    构建。要更喜欢速度而不是大小,您可以添加到您的构建中。gradle

    android {
      buildTypes {
        release {
          externalNativeBuild.cmake.cFlags "-O3"
        }
      }
    }
    
  • 您的C代码(实际上是原始Java代码)需要进行一些优化。主要的低效性(据我所知)是你重新计算每个U和V值四次。解决的办法很简单,那就是

  • 进一步优化可以避免内环中的乘法(在外环中,也可以消除乘法,但影响可以忽略不计):

  • #包括
    #包括
    #包括
    #包括
    #包括
    JNIEXPORT jbyteArray JNICALL
    Java公司应用程序工具NV21FrameRotator旋转器(JNIEnv*env,jclass thiz,
    jbyteArray数据,jbyteArray输出,
    jint宽度、jint高度、jint旋转){
    时钟开始、结束;
    使用双cpu时间;
    开始=时钟();
    jbyte*dataPtr=(*env)->GetByteArrayElements(env,data,NULL);
    jbyte*outputPtr=(*env)->GetByteArrayElements(env,output,NULL);
    无符号整数帧大小=宽度*高度;
    布尔交换=旋转%180!=0;
    bool xflip=旋转%270!=0;
    bool yflip=旋转>=180;
    无符号整数wOut=swap?高度:宽度;
    无符号整数=交换?宽度:高度;
    无符号整数yIn=0;
    for(无符号整数j=0;jReleaseByteArrayElements(环境、数据、数据PTR、JNI_中止);
    (*env)->ReleaseByteArrayElements(env,output,outputPtr,0);
    结束=时钟();
    使用的cpu时间=((双精度)(结束-开始))/每秒时钟数;
    __安卓系统日志打印(安卓系统日志错误,“NV21FrameRotator”、“%.1f ms”、cpu使用时间*1000);
    返回输出;
    }
    
  • 在实际设备上比较Java和C的性能,仿真器将不会产生可靠的结果

  • 比较Java和C在版本构建时的性能,C中的调试速度很慢,而Java仍然得到完全的JIT(和AOT)优化

  • 您可以为您的场景寻找最佳优化选择。默认情况下,版本将使用
    -Oz
    构建。要更喜欢速度而不是大小,您可以添加到您的构建中。gradle

    android {
      buildTypes {
        release {
          externalNativeBuild.cmake.cFlags "-O3"
        }
      }
    }
    
  • 您的C代码(实际上是原始Java代码)需要进行一些优化。主要的低效性(据我所知)是你重新计算每个U和V值四次。解决的办法很简单,那就是

  • 进一步优化可以避免内环中的乘法(在外环中,也可以消除乘法,但影响可以忽略不计):

  • #包括
    #包括
    #包括
    #包括
    #包括
    JNIEXPORT jbyteArray JNICALL
    Java公司应用程序工具NV21FrameRotator旋转器(JNIEnv*env,jclass thiz,
    jbyteArray数据,jbyteArray输出,
    jint宽度、jint高度、jint旋转){
    时钟开始、结束;
    使用双cpu时间;
    开始=时钟();
    jbyte*dataPtr=(*env)->GetByteArrayElements(env,data,NULL);
    jbyte*outputPtr=(*env)->GetByteArrayElements(env,output,NULL);
    无符号整数帧大小=宽度*高度;
    布尔交换=旋转%180!=0;
    bool xflip=旋转%270!=0;
    
    android {
      buildTypes {
        release {
          externalNativeBuild.cmake.cFlags "-O3"
        }
      }
    }