在 Android 模拟器 Shell 下运行 ncnn 推理的性能排查记录

在 Android 模拟器 Shell 下运行 ncnn 推理的性能排查记录

本文是对 《ChineseOCR Lite Android 纯C++版 - 初学者学习项目》 中一个未解问题的深入探讨。在那篇文章中,我将 chineseocr_lite 的 Android 版本去掉了 Java/JNI 层,编译为独立的 main 可执行文件,通过 adb shell 直接在 Android 设备上运行。文章末尾记录了一个现象:在模拟器上通过 App 调用很快,但直接通过 Shell 调用就很慢,当时仅做了初步猜测,并未深入排查。本文将完整还原这个问题的排查过程。

背景

在上一篇文章中,我完成了 chineseocr_lite 的纯 C++ 移植,去掉了所有 Java 层,使用标准 main 入口和命令行参数接收图片路径,支持在 Android 真机和模拟器上直接运行。

编译目标架构为 arm64-v8a,推送到模拟器执行后,模型能跑通,但速度极慢,处理一张 1080P 图片需要几秒。而同样的模型在手机 App 上却很快。为了找出原因,我进行了一系列排查。

排查过程

1. 为什么 App 调用快,Shell 调用慢?

最初我怀疑是 GPU 加速的问题。在代码中设置 net.opt.use_vulkan_compute = true 后,App 端能运行且 CPU 占用率不高,而 Shell 端 CPU 直接拉满。

我通过 logcat 抓取日志分析:

vbnet 复制代码
vulkan version is 4206869
DbNet use vulkan compute
Load vulkan library directly. so 0x31254b642b2d987d
Failed to get gpu service
searching for layers in '/data/app/.../lib/x86_64'
no vulkan device

日志中的 vulkan version is 4206869 是标准的 Vulkan API 版本编码(对应 1.3.277),仅凭版本号无法判断具体是哪种 Vulkan 实现(硬件驱动、软件模拟层或其他)。但 Failed to get gpu service 和 no vulkan device 表明 ncnn 最终没有找到可用的 Vulkan 物理设备,实际回退到了 CPU 计算。

因此,App 下并没有真正使用 GPU 硬件加速。 那为什么 App 比 Shell 快?

真正的原因是 Android 的 ABI 自动选择机制:

当通过 JNI/App 调用时,Android 的包管理系统会根据设备架构自动从 APK 中选择最合适的 so 库。我的 APK 中同时包含了 x86_64 和 arm64-v8a 等多种架构的库。在 x86_64 的电脑模拟器上,系统会自动选择 x86_64 版本的 so,实现原生执行,无需指令集转译。

而 Shell 中直接运行的 main 可执行文件,是我单独编译的 arm64-v8a 版本。在 x86_64 的电脑模拟器上运行 ARM 程序时,必须经过指令集转译层实时翻译,这个开销非常大,导致速度极慢。

运行方式 实际执行架构 是否需要转译 速度
App (JNI) x86_64(系统自动选择) 否,原生执行 快
Shell (./main) arm64-v8a(强制运行) 是,实时转译 极慢

2. 为什么手机上的模拟器比电脑模拟器快?

同一个 arm64-v8a 的 main 程序,在手机上安装的模拟器(如 VMOS、光速虚拟机等)的 Shell 里运行很快,但在电脑模拟器(如 MuMu)的 Shell 里很慢。

原因分析:

环境 宿主机架构 虚拟机内架构 程序架构 执行方式
手机模拟器(VMOS等) ARM64 ARM64 arm64-v8a 原生执行
电脑模拟器(MuMu等) x86_64 x86_64 arm64-v8a 指令集转译

手机本身是 ARM 架构,手机上的虚拟机里跑的也是 ARM 系统。因此 arm64-v8a 的程序在手机模拟器里是原生执行,没有转译开销。

而电脑是 x86_64 架构,在 Shell 中运行 ARM 程序时,必须经过转译层逐条翻译指令。特别是 ncnn 大量使用了 ARM NEON 向量指令,转译这类指令的效率极低,即使开启多线程,每个线程都在做"翻译+执行",整体速度依然很慢。

3. 编译 x86_64 后的动态库链接问题

既然电脑模拟器是 x86_64 架构,我尝试将 main 编译为 x86_64 版本,绕过指令集转译。

编译成功后再次运行,遇到了新的报错:

vbnet 复制代码
CANNOT LINK EXECUTABLE "./main": library "libicu.so" not found: needed by /system/lib64/libharfbuzz_ng.so in namespace (default)

程序依赖了 OpenCV,而 OpenCV 中的某些模块(如涉及文字渲染的 highgui/harfbuzz)底层依赖了系统的 libicu.so。由于 Android 的命名空间隔离机制,默认情况下 Shell 进程的 Linker 无法访问系统级库。

我尝试从系统中拷贝 libicu.so 并设置 LD_LIBRARY_PATH:

bash 复制代码
cp /system/lib64/arm64/libicu.so ./
export LD_LIBRARY_PATH=/sdcard/Download:$LD_LIBRARY_PATH
./main

结果报出架构不匹配的错误:

java 复制代码
"/storage/emulated/0/Download/libicu.so" is for EM_AARCH64 (183) instead of EM_X86_64 (62)

原因分析:

我拷贝的 /system/lib64/arm64/libicu.so 是模拟器专门为转译层准备的 ARM64 库,而当前运行的 main 是 x86_64 架构,Linker 拒绝加载不同架构的动态库。

4. 定位正确的 x86_64 系统库

我重新在系统中搜索 libicu.so:

bash 复制代码
find /system -name libicu.so
# /system/etc/mumu-configs/translators/38816/lib64/arm64/libicu.so
# /system/lib64/arm64/libicu.so
# /system/apex/com.android.i18n/lib64/libicu.so

前两个路径都带有 arm64 标识,是给转译层用的。而 /system/apex/ 目录是 Android 较新版本用来存放核心系统模块的地方,这里的库才是对应系统架构(即 x86_64)的原生库。

由于 Shell 进程默认不会搜索 APEX 路径,我需要手动将这个路径加入环境变量:

bash 复制代码
export LD_LIBRARY_PATH=/system/apex/com.android.i18n/lib64/:$LD_LIBRARY_PATH
./main s.raw

这次程序顺利运行,输出结果:

less 复制代码
width: 1080, height: 1920
size:8294400
use time:446.956
ocr result: 温馨提示

执行时间从原来的几十秒骤降到了 446 毫秒,达到了正常水平。

总结

在 Android 模拟器的 Shell 下运行 C++ 推理程序,有几个关键点需要注意:

  1. App 与 Shell 的性能差异本质: 不是 GPU 加速,而是 ABI 架构匹配问题。APK 包含多种架构的 so 库,Android 系统会自动为设备选择最优架构(如 x86_64 模拟器选 x86_64 so),实现原生执行;而 Shell 中直接运行的单一架构可执行文件,在跨架构时必须经过指令集转译,性能损失巨大。

  2. 架构必须匹配: 在 x86_64 电脑模拟器上,务必编译 x86_64 架构的程序。跨架构的指令集转译会带来巨大的性能损耗,导致多线程也难以奏效。而手机上的 ARM 虚拟机不存在这个问题,因为 arm64-v8a 程序可以原生执行。

  3. 动态库依赖与隔离: Android 的命名空间隔离会阻止 Shell 访问部分系统库。如果遇到 not found 且确认库存在于系统中,需排查是否是架构不匹配(误用了 arm64 目录下的库),或是需要通过 LD_LIBRARY_PATH 显式指定类似 /system/apex/... 这种被隐藏的库路径。

此外,对于纯推理的命令行程序,建议在 CMake 阶段就剥离 OpenCV 中不必要的 UI 模块(如 highgui),可以从源头减少这类系统级动态库的依赖问题。

相关推荐
还卿一钵无情泪8 小时前
Unsloth 微调 构建自己的大模型 没有GPU也能微调
linux·开发语言·人工智能·python·大模型·nlp·unsloth
高山有多高8 小时前
【Linux笔记】IO模型
linux
不会就选b8 小时前
Linux之TCP理论<1>
linux·运维·服务器
小π军9 小时前
操作系统面试题
java·linux·服务器
Lsetea10 小时前
curl用IP测试HTTPS报证书不匹配:--resolve与Host头别混用
linux·https·ssl证书·openssl·curl
Hao28510 小时前
Windows11 下把 WSL2 Ubuntu 26.04 装到 D 盘完整
linux
Android系统攻城狮10 小时前
Linux Gstreamer深度解析之gst_audio_encoder_get_frame_samples_min调用流程与实战(六十)
linux·运维·服务器·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
_upupup11 小时前
Linux中的自动化构建——make/Makefile
linux·运维·自动化
HEJOO911 小时前
Linux 设备驱动系列——使用全局工作队列
java·linux·算法
slimer12 小时前
07.进制转换上篇:位权展开与十进制互转
linux