Android neon 優(yōu)化實(shí)踐示例
搭建實(shí)驗(yàn)環(huán)境
首先新建一個(gè)包含native代碼的項(xiàng)目:

然后在gradle中添加對(duì)neon的支持:
externalNativeBuild {
cmake {
cppFlags "-std=c++14"
arguments "-DANDROID_ARM_NEON=TRUE"
}
}這樣,項(xiàng)目就可以支持neon加速了。
小試牛刀
一個(gè)最簡(jiǎn)單的neon編程的流程大致是這樣的: 1、裝載數(shù)據(jù)到neon寄存器 2、執(zhí)行運(yùn)算 3、從neon寄存器中把結(jié)果寫回內(nèi)存。
沒有例子不知從何說起,先上一個(gè)超級(jí)簡(jiǎn)單的例子吧:
#include <jni.h>
#include <string>
#include <arm_neon.h>
#include <android/log.h>
#define LOG_TAG "TEST_NEON"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
extern "C"{
void test()
{
int16_t result[8];
int8x8_t a = vdup_n_s8(121);
int8x8_t b = vdup_n_s8(2);
int16x8_t c;
c = vmull_s8(a,b);
vst1q_s16(result,c);
for(int i=0;i<8;i++){
LOGD("data[%d] is %d ",i,result[i]);
}
}
JNIEXPORT jstring
JNICALL
Java_com_example_javer_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
test();
return env->NewStringUTF(hello.c_str());
}
}執(zhí)行結(jié)果:
09-07 12:03:08.335 11709-11709/? D/TEST_NEON:
data[0] is 242
data[1] is 242
data[2] is 242
data[3] is 242
data[4] is 242
data[5] is 242
data[6] is 242
data[7] is 242
代碼中,test函數(shù)中實(shí)現(xiàn)了兩個(gè)64位neon寄存器的乘法。
vdup是數(shù)據(jù)復(fù)制指令,這里把128這個(gè)8位的數(shù)復(fù)制到一個(gè)64位的寄存器中,64位能存放8個(gè)8位的數(shù),因此,此時(shí)a指向的neon寄存器存放了8個(gè)128。
兩個(gè)8位的數(shù)相乘,結(jié)果可能是16位的,因此,結(jié)果需要用一個(gè)128位的寄存器來保存。int16x8就表示的是一個(gè)128位的寄存器。
vmull_s8把a(bǔ),b相乘,并將結(jié)果保存在c中。c指向的是neon的128位寄存器,因此,我們需要把結(jié)果寫回內(nèi)存。
vst1q_s16把c中的數(shù)據(jù)協(xié)會(huì)result指向的內(nèi)存中。
這是一個(gè)簡(jiǎn)單的測(cè)試neon指令的代碼,通過這個(gè)代碼我們能清晰的認(rèn)識(shí)到neon加速的原理:一次裝載8個(gè)8位的數(shù)到64位寄存器,一條指令能把實(shí)現(xiàn)兩個(gè)8*8的數(shù)據(jù)塊的乘法。
這樣效率不就接近提升8倍么?當(dāng)然沒有這么理想,畢竟裝載數(shù)據(jù)和寫回?cái)?shù)據(jù)也是需要時(shí)間的。
實(shí)戰(zhàn)嘗試
接下來,嘗試一個(gè)比較簡(jiǎn)單的rgb轉(zhuǎn)灰度圖的code:
void normal_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
{
int i;
for (i=0; i<n; 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);
}
}
void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
{
int i;
uint8x8_t rfac = vdup_n_u8 (77);
uint8x8_t gfac = vdup_n_u8 (151);
uint8x8_t bfac = vdup_n_u8 (28);
n/=8;
for (i=0; i<n; i++)
{
uint16x8_t temp;
uint8x8x3_t rgb = vld3_u8 (src);
uint8x8_t result;
temp = vmull_u8 (rgb.val[0], rfac);
temp = vmlal_u8 (temp,rgb.val[1], gfac);
temp = vmlal_u8 (temp,rgb.val[2], bfac);
result = vshrn_n_u16 (temp, 8);
vst1_u8 (dest, result);
src += 8*3;
dest += 8;
}
}
void test1()
{
//準(zhǔn)備一張圖片,使用軟件模擬生成,格式為rgb rgb ..
uint32_t const array_size = 2048*2048;
uint8_t * rgb = new uint8_t[array_size*3];
for(int i=0;i<array_size;i++){
rgb[i*3]=234;
rgb[i*3+1]=94;
rgb[i*3+2]=23;
}
//灰度圖大小為rgb的1/3
uint8_t * gray = new uint8_t[array_size];
struct timeval tv1,tv2;
gettimeofday(&tv1,NULL);
normal_convert(gray,rgb,array_size);
gettimeofday(&tv2,NULL);
LOGD("pure cpu cost time:%ld",(tv2.tv_sec-tv1.tv_sec)*1000000+(tv2.tv_usec-tv1.tv_usec));
gettimeofday(&tv1,NULL);
neon_convert(gray,rgb,array_size);
gettimeofday(&tv2,NULL);
LOGD("neon cost time:%ld",(tv2.tv_sec-tv1.tv_sec)*1000000+(tv2.tv_usec-tv1.tv_usec));
delete[] rgb;
delete[] gray;
}
JNIEXPORT jstring
JNICALL
Java_com_example_javer_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
test1();
return env->NewStringUTF(hello.c_str());
}具體的指令就不一一說明了,大家參考neon匯編指令集,對(duì)照著看就好。
純cpu耗時(shí)53ms,neon優(yōu)化后耗時(shí)43ms,提升非常有限,跟提升近8倍的預(yù)期相差甚遠(yuǎn)。這主要是因?yàn)閏轉(zhuǎn)換為匯編后,生成的匯編指令不夠簡(jiǎn)潔,使得效率大大降低。因此,接下來,使用匯編對(duì)代碼進(jìn)行優(yōu)化。
CMake添加匯編支持
為了在Cmake中編譯匯編文件,我們需要在CMakeLists.txt文件中申明對(duì)匯編語言的支持,添加ENABLE_LANGUAGE(ASM)即可實(shí)現(xiàn)對(duì)匯編的支持,接著將匯編文件添加進(jìn)來,此處貼出完整的CMakeLists.txt文件供大家參考:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
ENABLE_LANGUAGE(ASM)
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/Neon.S
src/main/cpp/native-lib.cpp
)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )實(shí)現(xiàn)匯編Neon優(yōu)化
然后在cpp文件中申明:
void neon_asm_convert(uint8_t * dest, uint8_t * src,int n);
注意,這個(gè)申明是包含在extern “C”中的。 然后在Neon.S中實(shí)現(xiàn)neon_asm_convert函數(shù):
.globl neon_asm_convert
neon_asm_convert:
# 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 }為了對(duì)比結(jié)果的正確性,專門寫了個(gè)比對(duì)函數(shù):
int compare(uint8_t *a,uint8_t* b,int n)
{
for(int i=0;i<n;i++){
if(a[i]!=b[i]){
return -1;
}
}
return 0;
}并將結(jié)果打印在時(shí)間后面:
LOGD("neon c cost time:%ld,result is %d",(tv2.tv_sec-tv1.tv_sec)*1000000+(tv2.tv_usec-tv1.tv_usec),result);三者對(duì)比:
09-07 17:12:19.946 25861-25861/com.example.javer.myapplication D/TEST_NEON: pure cpu cost time:57073
09-07 17:12:20.012 25861-25861/com.example.javer.myapplication D/TEST_NEON: neon c cost time:45460,result is 0
09-07 17:12:20.034 25861-25861/com.example.javer.myapplication D/TEST_NEON: neon asm cost time:3397,result is 0
09-07 17:12:25.271 25861-25861/com.example.javer.myapplication D/TEST_NEON: pure cpu cost time:57404
09-07 17:12:25.336 25861-25861/com.example.javer.myapplication D/TEST_NEON: neon c cost time:45166,result is 0
09-07 17:12:25.359 25861-25861/com.example.javer.myapplication D/TEST_NEON: neon asm cost time:3493,result is 0
最終發(fā)現(xiàn),匯編執(zhí)行的結(jié)果完全正確,時(shí)間提升超過了16倍?。。。。。。。。。?! 我甚至不敢相信能提升這么多。。??蓪?duì)比的結(jié)果是完全一樣啊?。∵@…….
如果程序有問題,感謝大神指出。
最后附完整代碼: native_lib.cpp:
#include <jni.h>
#include <string>
#include <arm_neon.h>
#include <android/log.h>
#define LOG_TAG "TEST_NEON"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
extern "C"{
void neon_asm_convert(uint8_t * dest, uint8_t * src,int n);
void test()
{
int16_t result[8];
int8x8_t a = vdup_n_s8(121);
int8x8_t b = vdup_n_s8(2);
int16x8_t c;
c = vmull_s8(a,b);
vst1q_s16(result,c);
for(int i=0;i<8;i++){
LOGD("data[%d] is %d ",i,result[i]);
}
}
void normal_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
{
int i;
for (i=0; i<n; 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);
}
}
void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
{
int i;
uint8x8_t rfac = vdup_n_u8 (77);
uint8x8_t gfac = vdup_n_u8 (151);
uint8x8_t bfac = vdup_n_u8 (28);
n/=8;
for (i=0; i<n; i++)
{
uint16x8_t temp;
uint8x8x3_t rgb = vld3_u8 (src);
uint8x8_t result;
temp = vmull_u8 (rgb.val[0], rfac);
temp = vmlal_u8 (temp,rgb.val[1], gfac);
temp = vmlal_u8 (temp,rgb.val[2], bfac);
result = vshrn_n_u16 (temp, 8);
vst1_u8 (dest, result);
src += 8*3;
dest += 8;
}
}
int compare(uint8_t *a,uint8_t* b,int n)
{
for(int i=0;i<n;i++){
if(a[i]!=b[i]){
return -1;
}
}
return 0;
}
void test1()
{
//準(zhǔn)備一張圖片,使用軟件模擬生成,格式為rgb rgb ..
uint32_t const array_size = 2048*2048;
uint8_t * rgb = new uint8_t[array_size*3];
for(int i=0;i<array_size;i++){
rgb[i*3]=234;
rgb[i*3+1]=94;
rgb[i*3+2]=23;
}
//灰度圖大小為rgb的1/3
uint8_t * gray_cpu = new uint8_t[array_size];
uint8_t * gray_neon = new uint8_t[array_size];
uint8_t * gray_neon_asm = new uint8_t[array_size];
struct timeval tv1,tv2;
gettimeofday(&tv1,NULL);
normal_convert(gray_cpu,rgb,array_size);
gettimeofday(&tv2,NULL);
LOGD("pure cpu cost time:%ld",(tv2.tv_sec-tv1.tv_sec)*1000000+(tv2.tv_usec-tv1.tv_usec));
gettimeofday(&tv1,NULL);
neon_convert(gray_neon,rgb,array_size);
gettimeofday(&tv2,NULL);
bool result = compare(gray_cpu,gray_neon,array_size);
LOGD("neon c cost time:%ld,result is %d",(tv2.tv_sec-tv1.tv_sec)*1000000+(tv2.tv_usec-tv1.tv_usec),result);
gettimeofday(&tv1,NULL);
neon_asm_convert(gray_neon_asm,rgb,array_size);
gettimeofday(&tv2,NULL);
result = compare(gray_cpu,gray_neon_asm,array_size);
LOGD("neon asm cost time:%ld,result is %d",(tv2.tv_sec-tv1.tv_sec)*1000000+(tv2.tv_usec-tv1.tv_usec),result);
delete[] rgb;
delete[] gray_cpu;
delete[] gray_neon;
delete[] gray_neon_asm;
}
JNIEXPORT jstring
JNICALL
Java_com_example_javer_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
test1();
return env->NewStringUTF(hello.c_str());
}
}Neon.S
.globl neon_asm_convert
neon_asm_convert:
# 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 }以上就是Android neon 優(yōu)化實(shí)踐示例的詳細(xì)內(nèi)容,更多關(guān)于Android neon 優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決Android Device Monitor 的 File Explorer 中無法打開某些文件夾的問題
這篇文章主要介紹了解決Android Device Monitor 的 File Explorer 中無法打開某些文件夾的問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
詳解Android——藍(lán)牙技術(shù) 帶你實(shí)現(xiàn)終端間數(shù)據(jù)傳輸
藍(lán)牙技術(shù)在智能硬件方面有很多用武之地,本篇文章主要介紹了Android——藍(lán)牙技術(shù),實(shí)現(xiàn)兩個(gè)終端間數(shù)據(jù)的傳輸,有興趣的朋友可以了解一下。2016-12-12
Android NDK開發(fā)(C語言基本數(shù)據(jù)類型)
這篇文章主要介紹了Android NDK開發(fā)中,C語言基本數(shù)據(jù)類型,主要以C語言包含的數(shù)據(jù)類型及基本類型展開相關(guān)資料,需要的朋友可以參考一下2021-12-12
Android TextView Marquee的應(yīng)用實(shí)例詳解
這篇文章主要介紹了Android TextView Marquee的應(yīng)用實(shí)例詳解的相關(guān)資料,這里說明使用方法及簡(jiǎn)單實(shí)例和注意實(shí)現(xiàn),需要的朋友可以參考下2017-08-08
詳解android 用webview加載網(wǎng)頁(https和http)
這篇文章主要介紹了詳解android 用webview加載網(wǎng)頁(https和http),詳細(xì)的介紹了兩個(gè)錯(cuò)誤的解決方法,有興趣的可以了解一下2017-11-11
Android串口開發(fā)之使用JNI實(shí)現(xiàn)ANDROID和串口通信詳解
這篇文章主要給大家介紹了關(guān)于Android串口開發(fā)之使用JNI實(shí)現(xiàn)ANDROID和串口通信的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01
Android封裝MVP實(shí)現(xiàn)登錄注冊(cè)功能
這篇文章主要為大家詳細(xì)介紹了Android封裝MVP實(shí)現(xiàn)登錄注冊(cè)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
Android Activity的啟動(dòng)過程源碼解析
這篇文章主要介紹了Android Activity的啟動(dòng)過程源碼解析,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
View觸發(fā)機(jī)制API實(shí)現(xiàn)GestureDetector OverScroller詳解
這篇文章主要為大家介紹了View觸發(fā)機(jī)制API實(shí)現(xiàn)GestureDetector OverScroller詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11

