转换为C / NDK / JNI的代码效率不如Java原始代码

如何解决转换为C / NDK / JNI的代码效率不如Java原始代码

这是我第一次不那么沉迷于NDK。

出于性能目的,我想将this code重写为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,dataPtr,0);
    (*env)->ReleaseByteArrayElements(env,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;
}

这两个代码片段(链接到Java和更高版本)都可以很好地工作,但是当我测量处理持续时间时,看起来在同一设备上,Java版本大约需要7毫秒(Log.i( Java侧日志)和C 12-13毫秒。 。不应该更快,为什么不呢?渔获物在哪里?

long micros = System.nanoTime() / 1000;
// ~7ms,Java
//data = rotateNV21(inputData,width,height,rotateCameraDegrees);
// ~12-13ms,C
NV21FrameRotator.rotateNV21(inputData,rotateCameraDegrees);
Log.d(TAG,"Last frame processing duration: " + (System.nanoTime() / 1000 - micros) + "µs");

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

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_commands.json用于armeabi-v7a(旧设备,我只在建造这一个)

[
{
  "directory": "...app/.cxx/cmake/basicRelease/armeabi-v7a","command": "...sdk\\ndk\\21.0.6113669\\toolchains\\llvm\\prebuilt\\windows-x86_64\\bin\\clang.exe --target=armv7-none-linux-androideabi21 --gcc-toolchain=...sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/windows-x86_64 --sysroot=...sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/windows-x86_64/sysroot -DNV21FrameRotator_EXPORTS  -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -march=armv7-a -mthumb -Wformat -Werror=format-security  -Oz -DNDEBUG  -fPIC   -o CMakeFiles\\NV21FrameRotator.dir\\NV21FrameRotator.c.o   -c ...app\\src\\main\\cpp\\NV21FrameRotator.c","file": "...app\\src\\main\\cpp\\NV21FrameRotator.c"
}
]

CMakeFile

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,以避免可能的字节数组副本。

,
  1. 将Java的性能与真实设备上的C进行比较,仿真器不会产生可靠的结果。

  2. 在发行版本中比较Java与C的性能,在C中进行调试的速度很慢,而Java仍然获得了完整的JIT(和AOT)优化。

  3. 您可能会为您的方案寻找最佳的优化选择。默认情况下,版本将使用-Oz构建。要优先选择速度而不是大小,可以将其添加到 build.gradle

    android {
      buildTypes {
        release {
          externalNativeBuild.cmake.cFlags "-O3"
        }
      }
    }
    
  4. 您的C代码(实际上是原始Java代码)进行了一些优化。 (据我所知)主要的效率低下是您重新计算了每个U和V值四次。简单的解决方法是split the loops

  5. 进一步的优化可以避免内循环中的乘法(在外循环中也可以消除,但是效果可以忽略不计):

#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;

    unsigned int wOut = swap ? height : width;
    unsigned int hOut = swap ? width : height;
    unsigned int yIn = 0;

    for (unsigned int j = 0; j < height; j++) {

        unsigned int iSwapped = swap ? j : 0;
        unsigned int jSwapped = swap ? 0 : j;
        unsigned int iOut = xflip ? wOut - iSwapped - 1 : iSwapped;
        unsigned int jOut = yflip ? hOut - jSwapped - 1 : jSwapped;
        unsigned int yOut = jOut * wOut + iOut;

        for (unsigned int i = 0; i < width; i++) {
            outputPtr[yOut] = dataPtr[yIn];
            if (swap) {
                yOut += yflip ? -wOut : wOut;
            } else {
                yOut += xflip ? -1 : 1;
            }
            yIn++;
        }
    }

    unsigned int uIn = frameSize;

    for (unsigned int j = 0; j < height; j+=2) {

        unsigned int iSwapped = swap ? j : 0;
        unsigned int jSwapped = swap ? 0 : j;
        unsigned int iOut = xflip ? wOut - iSwapped - 1 : iSwapped;
        unsigned int jOut = yflip ? hOut - jSwapped - 1 : jSwapped;
        unsigned int uOut = frameSize + (jOut / 2) * wOut + (iOut & ~1u);

        for (unsigned int i = 0; i < width; i+=2) {
            unsigned int vIn = uIn + 1;
            unsigned int vOut = uOut + 1;

            outputPtr[uOut] = dataPtr[uIn];
            outputPtr[vOut] = dataPtr[vIn];

            if (swap) {
                uOut += yflip ? -wOut : wOut;
            } else {
                uOut += xflip ? -2 : 2;
            }
            uIn += 2;
        }
    }

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

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

    __android_log_print(ANDROID_LOG_ERROR,"NV21FrameRotator","%.1f ms",cpu_time_used * 1000);

    return output;
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-