Native开发工具之CPU-和架构(三)

/lib/armeabi/libfoo.so
/lib/armeabi-v7a/libfoo.so
/lib/arm64-v8a/libfoo.so
/lib/x86/libfoo.so
/lib/x86_64/libfoo.so

注意:搭载 4.0.3 或更早版本、基于 ARMv7 的 Android 设备从 armeabi 目录(而非 armeabi-v7a 目录,如果两个目录都存在)安装原生库。这是因为在 APK 中,/lib/armeabi/ 在 /lib/armeabi-v7a/ 后面。从 4.0.4 开始,此问题已修复。

Android 平台 ABI 支持

Android 系统在运行时知道它支持哪些 ABI,因为版本特定的系统属性会指示:

  • 设备的主要 ABI,与系统映像本身使用的机器代码对应。
  • (可选)与系统映像也支持的其他 ABI 对应的辅助 ABI。

此机制确保系统在安装时从软件包提取最佳机器代码。

为实现最佳性能,应直接针对主要 ABI 进行编译。例如,基于 ARMv5TE 的典型设备只会定义主要 ABI:armeabi。相反,基于 ARMv7 的典型设备将主要 ABI 定义为 armeabi-v7a,并将辅助 ABI 定义为 armeabi,因为它可以运行为每个 ABI 生成的应用原生二进制文件。

64 位设备也支持其 32 位变体。以 arm64-v8a 设备为例,该设备也可以运行 armeabi 和 armeabi-v7a 代码。但请注意,如果应用以 arm64-v8a 为目标,而非依赖于运行 armeabi-v7a 版应用的设备,应用在 64 位设备上的性能要好得多。

许多基于 x86 的设备也可运行 armeabi-v7a 和 armeabi NDK 二进制文件。对于这些设备,主要 ABI 将是 x86,辅助 ABI 是 armeabi-v7a

安装时自动解压缩原生代码

安装应用时,软件包管理器服务将扫描 APK,并查找以下形式的任何共享库:

lib//lib.so

如果未找到,并且您已定义辅助 ABI,该服务将扫描以下形式的共享库:

lib//lib.so

找到所需的库时,软件包管理器会将它们复制到应用的 data 目录 (data/data/<package_name>/lib/) 下的 /lib/lib<name>.so

如果根本没有共享对象文件,应用也会编译并安装,但在运行时会崩溃。

三、处理CPU功能

ABI:使用预处理器的预定义宏

通常,在编译时使用 #ifdef 及以下各项确定 ABI 最为方便:

  • 对于 32 位 ARM,使用 __arm__
  • 对于 64 位 ARM,使用 __aarch64__
  • 对于 32 位 X86,使用 __i386__
  • 对于 64 位 X86,使用 __x86_64__

请注意:32 位 X86 称为 __i386__,而不是 __x86__,这可能与您预想的有所不同!

CPU 核心计数:使用 libc 的 sysconf(3)

sysconf(3) 既可以查询 _SC_NPROCESSORS_CONF(系统中的 CPU 核心数),又可以查询 _SC_NPROCESSORS_ONLN(当前在线的 CPU 核心数)。

功能:使用 libc 的 getauxval(3)

自 API 级别 18 开始,Android 的 C 库开始支持 etauxval(3)AT_HWCAP 和 AT_HWCAP2 参数会返回列出特定 CPU 功能的位掩码。请参阅 NDK 中的各种 hwcap.h 头文件以获取要进行比较的常量,如用于 arm64 SHA512 指令的 HWCAP_SHA512,或用于 arm Thumb 整数除法指令的 HWCAP_IDIVT

Google cpu_features 库

AT_HWCAP 的一个问题是有时设备会出错。例如,一些旧设备宣称拥有整数除法指令,但实际上并没有。

Google 的 cpu_features 库凭借其对特定 SoC 的了解(通过解析 /proc/cpuinfo 掌握相应的 SoC),解决了此类问题。

另一个解决方案是为 SIGILL 安装信号处理程序,然后直接尝试执行相关指令。例如,BoringSSL/OpenSSL 使用的就是这种方法。

NDK cpufeatures 库

NDK 提供了一个名为 cpufeatures 的小型库,其功能类似于 getauxval(3),但它也可用于 API 级别 18 之前的版本。与其他 cpu_features 库不同,它对特定 SoC 并无额外的了解。

NDK cpufeatures API

uint64_t android_getCpuFeatures();

返回一组位标记,每个标记代表一个 CPU 系列特定的功能。本部分其余内容介绍了各个系列的功能。

32 位 ARM CPU 系列

以下标记适用于 32 位 ARM CPU 系列:

ANDROID_CPU_ARM_FEATURE_VFPv2
表示设备的 CPU 支持 VFPv2 指令集。大多数 ARMv6 CPU 都支持此指令集。
ANDROID_CPU_ARM_FEATURE_ARMv7
表示设备的 CPU 支持 armeabi-v7a ABI 所支持的 ARMv7-A 指令集。此指令集同时支持 Thumb-2 和 VFPv3-D16 指令。此返回值还表示支持 VFPv3 硬件 FPU 扩展指令集。
ANDROID_CPU_ARM_FEATURE_VFPv3
表示设备的 CPU 支持 VFPv3 硬件 FPU 扩展指令集。
此值等同于 VFPv3-D16 指令集,后者只提供 16 个硬件双精度 FP 寄存器。
ANDROID_CPU_ARM_FEATURE_VFP_D32
表示设备的 CPU 支持 32 个(而不是 16 个)硬件双精度 FP 寄存器。即使有 32 个硬件双精度 FP 寄存器,也只有 32 个单精度寄存器映射至同一寄存器组。
ANDROID_CPU_ARM_FEATURE_NEON
表示设备的 CPU 支持 ARM Advanced SIMD (NEON) 向量扩展指令集。请注意,ARM 要求这些 CPU 也要实现 VFPv3-D32,VFPv3-D32 提供 32 个硬件 FP 寄存器(与 NEON 单元共享)。
ANDROID_CPU_ARM_FEATURE_VFP_FP16
表示设备的 CPU 支持在 16 位寄存器上执行浮点运算的指令。该功能是 VFPv4 规范的一部分。
ANDROID_CPU_ARM_FEATURE_VFP_FMA
表示设备的 CPU 支持 VFP 指令集的融合乘积累加扩展指令集。它也是 VFPv4 规范的一部分。
ANDROID_CPU_ARM_FEATURE_NEON_FMA
表示设备的 CPU 支持 NEON 指令集的融合乘积累加扩展指令集。它也是 VFPv4 规范的一部分。
ANDROID_CPU_ARM_FEATURE_IDIV_ARM
表示设备的 CPU 在 ARM 模式下支持整数除法。仅适用于更高型号的 CPU,例如 Cortex-A15。
ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2
表示设备的 CPU 在 Thumb-2 模式下支持整数除法。仅适用于更高型号的 CPU,例如 Cortex-A15。
ANDROID_CPU_ARM_FEATURE_iWMMXt
表示设备的 CPU 支持可添加 MMX 寄存器和指令的扩展指令集。该功能仅适用于少数几个基于 XScale 的 CPU。
ANDROID_CPU_ARM_FEATURE_LDREX_STREX
表示设备的 CPU 支持自 ARMv6 后可用的 LDREX 和 STREX 指令。借助专用监视器,这些指令结合起来可在内存中提供原子级更新。

64 位 ARM CPU 系列

以下标记适用于 64 位 ARM CPU 系列:
ANDROID_CPU_ARM64_FEATURE_FP
表示设备的 CPU 具有浮点单元 (FPU)。所有 Android ARM64 设备都必须支持此功能。
ANDROID_CPU_ARM64_FEATURE_ASIMD
表示设备的 CPU 具有高级 SIMD (ASIMD) 单元。所有 Android ARM64 设备都必须支持此功能。
ANDROID_CPU_ARM64_FEATURE_AES
表示设备的 CPU 支持 AES 指令。
ANDROID_CPU_ARM64_FEATURE_CRC32
表示设备的 CPU 支持 CRC32 指令。
ANDROID_CPU_ARM64_FEATURE_SHA1
表示设备的 CPU 支持 SHA1 指令。
ANDROID_CPU_ARM64_FEATURE_SHA2
表示设备的 CPU 支持 SHA2 指令。
ANDROID_CPU_ARM64_FEATURE_PMULL
表示设备的 CPU 支持 64 位 PMULL 和 PMULL2 指令。

32 位 x86 CPU 系列

以下标记适用于 32 位 x86 CPU 系列。

ANDROID_CPU_X86_FEATURE_SSSE3
表示设备的 CPU 支持 SSSE3 扩展指令集。
ANDROID_CPU_X86_FEATURE_POPCNT
表示设备的 CPU 支持 POPCNT 指令。
ANDROID_CPU_X86_FEATURE_MOVBE
表示设备的 CPU 支持 MOVBE 指令。此指令特定于某些 Intel IA-32 CPU,例如 Atom。
android_getCpuFeatures() 针对没有列出扩展指令集的 CPU 系列返回 0。

int android_getCpuCount(void);

注意:考虑到 sysconf(3),此函数通常无用。
返回系统中的 CPU 核心数,可能高于实际在线的核心数。

AndroidCpuFamily android_getCpuFamily();

注意:考虑到预处理器宏,此函数通常无用。
返回以下其中一个常量,代表设备所支持的 CPU 系列/架构:

  • ANDROID_CPU_FAMILY_ARM
  • ANDROID_CPU_FAMILY_X86
  • ANDROID_CPU_FAMILY_ARM64
  • ANDROID_CPU_FAMILY_X86_64

对于 64 位系统上的 32 位可执行文件,此函数返回 32 位架构。

将 NDK cpufeatures 与 ndk-build 搭配使用

cpufeatures 库可用作导入模块。要使用此库,请执行以下操作:

  • 将 cpufeatures 添加到 LOCAL_STATIC_LIBRARIES

  • 在源代码中 #include <cpu-features.h>

  • 在 Android.mk 的末尾导入 android/cpufeatures

以下是导入 cpufeatures 的 Android.mk 示例:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := your-module-name
LOCAL_SRC_FILES := …
LOCAL_STATIC_LIBRARIES := cpufeatures
include $(BUILD_SHARED_LIBRARY)

$(call import-module,android/cpufeatures)

三、NEON 支持

NDK 支持 ARM Advanced SIMD(通常称为 NEON),一种适用于 ARMv7 和 ARMv8 的可选扩展指令集。NEON 提供标量/向量指令和寄存器(与 FPU 共享),堪比 x86 中的 MMX/SSE/3DNow!。要运行 NEON,需要 VFPv3-D32(32 个硬件 FPU 64 位寄存器,而非最小值 16 个)。

并非所有基于 ARMv7 的 Android 设备都支持 NEON,但支持的设备可能会因 NEON 支持标量/向量指令而受益匪浅。

NDK 支持模块编译,甚至可以编译支持 NEON 的特定源文件。

使用 LOCAL_ARM_NEON

要让 NDK 编译支持 NEON 的所有源文件,请在模块定义中添加以下代码行:makefile LOCAL_ARM_NEON := true

如果您要编译仅包含 NEON 代码的静态或共享库,编译支持 NEON 的所有源文件可能特别有用。

使用 .neon 后缀

为 LOCAL_SRC_FILES 变量列出源文件时,您可以选择使用 .neon 后缀表示要编译支持 NEON 的单个文件。例如,以下示例会编译一个支持 NEON 的文件 (foo.c),以及另一个不支持 NEON 的文件 (bar.c):makefile LOCAL_SRC_FILES := foo.c.neon bar.c

您可将 .neon 后缀与 .arm 后缀相结合,后者指定用于非 NEON 指令的 32 位 ARM 指令集(而非 Thumb2)。在这种情况下,.arm 必须在 .neon 之前。例如:foo.c.arm.neon 可行,但 foo.c.neon.arm 不可行。

编译要求

NEON 支持适用于 [armeabi-v7a]( ) 和 [arm64-v8a]( ) ABI。如果 NDK 编译脚本在尝试借助 NEON 支持进行编译时遇到其他 ABI,NDK 编译脚本将退出。务必在 Android.mk 文件中添加如下检查代码:

define a static library containing our NEON code

ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
include $(CLEAR_VARS)
LOCAL_MODULE := mylib-neon
LOCAL_SRC_FILES := mylib-neon.c
LOCAL_ARM_NEON := true
include $(BUILD_STATIC_LIBRARY)
endif # TARGET_ARCH_ABI == armeabi-v7a

运行时检测

应用必须执行运行时检测,以确认支持 NEON 的机器代码能够在目标设备上运行。这是因为并非所有基于 ARMv7 的 Android 设备都支持 NEON。应用可以使用 NDK 随附的 cpufeatures 库执行此检查。

您应检查 android_getCpuFamily() 是否返回 ANDROID_CPU_FAMILY_ARM,以及 android_getCpuFeatures() 是否返回一个值,并且设置了 ANDROID_CPU_ARM_FEATURE_NEON 标记。例如:

#include <cpu-features.h>


if (android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM &&
(android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0)
{
// use NEON-optimized routines

}
else
{
// use non-NEON fallback routines instead

}

对 x86 提供跨平台支持

NDK 支持使用第三方 NEON_2_SSE.h 将您现有的 ARM SIMD (NEON) 内建函数跨平台编译为 x86 SSE 代码。要详细了解此内容,请参阅从 ARM NEON 到 Intel SSE:自动移植解决方案、提示与技巧

示例代码

hello-neon 示例举例说明了如何同时使用 cpufeatures 库和 NEON 内建函数。此示例实现了 C 版本的微小 FIR 过滤器循环基准,并针对支持 NEON 的设备实现了经 NEON 优化的基准。
原文链接 https://developer.android.google.cn/ndk/guides/cpu-arm-neon
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

总结

现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。

我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。

Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。

如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。

(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)

它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。

如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。

(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)

[外链图片转存中…(img-mYTlNizB-1720097754468)]

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值