在进行Android开发时,一般考虑加速某些算法处理速率时,需要使用NDK进行开发,

为了进一步加速程序执行速率,还可以进行汇编级别的优化。

比如采用 NEON 技术进行代码的优化,以实现模块处理效率的成倍增长。

在C/C++中使用内联汇编的用法如下:

asm(
"ADD R0,R0,#1 \n\t" // 使R0寄存器值增加1,后面加换行符和制表符是为了汇编代码的美观,其中有多条指令时 换行符是必须的
"SUB R0,R0,#1 " // 使R0寄存器的值减少1
);

然后是如何在NDK中编译使用这种指令,需要修改Android.mk和Application.mk两个文件:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# 这里填写要编译的源文件路径,这里只列举了一部分
LOCAL_SRC_FILES := NcHevcDecoder.cpp JNI_OnLoad.cpp TAppDecTop.cpp
# 默认包含的头文件路径
LOCAL_C_INCLUDES := \
$(LOCAL_PATH) \
$(LOCAL_PATH)/..
# -g 后面的一系列附加项目添加了才能使用 arm_neon.h 头文件 -mfloat-abi=softfp -mfpu=neon 使用 arm_neon.h 必须
LOCAL_CFLAGS := -D__cpusplus -g -mfloat-abi=softfp -mfpu=neon -march=armv7-a -mtune=cortex-a8
LOCAL_LDLIBS := -lz -llog
TARGET_ARCH_ABI :=armeabi-v7a
LOCAL_ARM_MODE := arm
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
# 采用NEON优化技术
LOCAL_ARM_NEON := true
endif
LOCAL_MODULE := avcodecinclude $(BUILD_STATIC_LIBRARY)
include $(BUILD_SHARED_LIBRARY)

下面是Application.mk,需要指定编译的目标平台类型:

APP_PROJECT_PATH := $(call my-dir)/..
APP_PLATFORM := android-10
APP_STL := stlport_static
APP_ABI := armeabi-v7a
APP_CPPFLAGS += -fexceptions

其中APP_ABI这句指定了编译的目标平台类型,可以针对不同平台进行优化。

当然这样指定了之后,就需要相应的设备支持NEON指令。

我的一个NDK应用,在使用上述配置之后,即NEON优化等,程序的性能提升了近一倍。

系统的处理延时由原来的 95ms左右降低到了 51ms。

后续可以使用NEON库进一步优化 NDK 程序代码,实现更加优化的结果。

PS:参考的文章有如下几篇,可能跟我的平台有所不同,所以我都不能完全照搬使用:

1、未使用NEON优化的代码

void reference_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
{
int i;
for (i=0; i
{
int r = *src++; // load red
int g = *src++; // load green
int b = *src++; // load blue
// build weighted average:
int y = (r*77)+(g*151)+(b*28);
// undo the scale by 256 and write to memory:
*dest++ = (y>>8);
}
}

2、使用NEON进行优化的代码

void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
{
int i;
uint8x8_t rfac = vdup_n_u8 (77); // 转换权值 R
uint8x8_t gfac = vdup_n_u8 (151); // 转换权值 G
uint8x8_t bfac = vdup_n_u8 (28); // 转换权值 B
n/=8;
for (i=0; i
{
uint16x8_t temp;
uint8x8x3_t rgb = vld3_u8 (src);
uint8x8_t result;
temp = vmull_u8 (rgb.val[0], rfac); // vmull_u8 每个字节(8bit)对应相乘,结果为每个单位2字节(16bit)
temp = vmlal_u8 (temp,rgb.val[1], gfac); // 每个比特对应相乘并加上
temp = vmlal_u8 (temp,rgb.val[2], bfac);
result = vshrn_n_u16 (temp, 8); // 全部移位8位
vst1_u8 (dest, result); // 转存运算结果
src += 8*3;
dest += 8;
}
}

3、汇编优化

// 这里针对生成的目标汇编代码进一步作了优化,优化的代码如下:
convert_asm_neon:
# r0: Ptr to destination data
# r1: Ptr to source data
# r2: Iteration count:
push {r4-r5,lr}
lsr r2, r2, #3
# build the three constants:
mov r3, #77
mov r4, #151
mov r5, #28
vdup.8 d3, r3
vdup.8 d4, r4
vdup.8 d5, r5
.loop:
# load 8 pixels:
vld3.8 {d0-d2}, [r1]!
# do the weight average:
vmull.u8 q3, d0, d3
vmlal.u8 q3, d1, d4
vmlal.u8 q3, d2, d5
# shift and store:
vshrn.u16 d6, q3, #8
vst1.8 {d6}, [r0]!
subs r2, r2, #1
bne .loop
pop { r4-r5, pc }

最后的结果对比:

C-version:             15.1 cycles per pixel.
NEON-version:       9.9 cycles per pixel.
Assembler:             2.0 cycles per pixel.