Android NDK之使用 arm-v7a 汇编实现两数之和

关键词: NDK armv7a WebRTC arm汇编 CMake

最近适配对讲程序,在webrtc的库编译的过程中,发现其为arm的平台定制了汇编程序以优化平方根倒数算法速度,上次写汇编还是8086的,借此机会初步尝试下android上arm汇编

具体jni工程建立就不介绍了,Android Studio直接可以从模板创建

工程目录如下

kryo@WSL1:/mnt/k/Android/NDK-Project/XXX/src/main$ tree
.
├── AndroidManifest.xml
├── cpp
│   ├── asm
│   │   ├── CMakeLists.txt
│   │   ├── asm_defines.h
│   │   ├── asm_jni.cpp
│   │   ├── asm_jni.h
│   │   ├── tow_sum_armv7a.S
│   │   └── tow_sum_cpp.cpp
└── java
└── com
└── kryo
├── asm
│   └── TowSumAsm.java
└── ...

1、C++接口编写

asm_jni.h

#ifndef TOW_SUM_AMS_TEST_H
#define TOW_SUM_AMS_TEST_H #include <jni.h> #ifdef USE_ASM
extern "C" int32_t
tow_sum_asm(int32_t *data_in, int32_t *data_out, int32_t data_len, int32_t ret_len, int32_t target);
#else
extern "C" int32_t
tow_sum_cpp(int32_t *data_in, int32_t *data_out, int32_t data_len, int32_t target);
#endif #endif //TOW_SUM_AMS_TEST_H

这里分别使用asm和c代码各自实现一个暴搜版本的两数之和接口。关于asm传递5个参数是有用意的,涉及到函数调用约定,armv7a前4个参数用寄存器传参,超过4个的用栈传递

2、汇编实现

写汇编时我习惯先参考C代码去推导

tow_sum_cpp.cpp

#include "asm_jni.h"

extern "C" int32_t tow_sum_cpp(int32_t *data_in, int32_t *data_out, int32_t data_len,int32_t target) {
for (int i = 0; i < data_len; ++i) {
for (int j = i + 1; j < data_len; ++j) {
if (data_in[i] + data_in[j] == target) {
data_out[0] = i;
data_out[1] = j;
return 0;
}
}
}
data_out[0] = 0;
data_out[1] = 0;
return -1;
}

以下是具体汇编代码的实现,基本每行都给出了注释

tow_sum_armv7a.S

@ Input:(
@ int32_t* data_in, -> r0 &data_in
@ int32_t* data_out,-> r1 &data_out
@ int32_t data_len, -> r2
@ int32_t ret_len, -> r3
@ int32_t target -> [sp])
@ Output: r0 32 bit unsigned integer
@
@ r4: i-index
@ r5: j-index
@ r6: target
@ r7: num1-buff
@ r8: num2-buff
@ r9: sum cache #include "asm_defines.h" GLOBAL_FUNCTION tow_sum
.align 4
DEFINE_FUNCTION tow_sum
push {r4-r11} @ 保存现场 ldr r6, [sp, #32] @ 保存了8个寄存器,偏移8*4bytes取得第5个参数 mov r4, #0 @ 初始化第一个数的索引 i
mov r5, #0 @ 初始化第二个数的索引 j LOOP_1:
sub r9, r2, #1 @ 数组长度-1
cmp r4, r9 @ 判断i是否数组最后一个
beq FAL @ 是就查找失败
mov r5, r4 @ j = i LOOP_2:
add r5, r5, #1 @ j ++
lsl r9, r4, #2 @ 把索引 i 乘4得到地址偏移量
ldr r7, [r0, r9] @ r7 = data_in[i],寄存器相对寻址, r0为 data_in的地址,加上偏移量取的数组元素
lsl r9, r5, #2
ldr r8, [r0, r9] @ 同上得到 r8 = data_in[j]
add r9, r8, r7 @ 两数之和
cmp r9, r6 @ 与目标做比较
beq SUC @ 成功
add r9, r5, #1 @ 没有成功
cmp r9, r2 @ if j < data_len
bne LOOP_2 @ then:下一轮j的查找
add r4, r4, #1 @ else: j没找到,把i++
b LOOP_1 @ 下一轮 i的查找 SUC:
str r4, [r1] @ data_out[0] = i
str r5, [r1, #4] @ data_out[1] = j
mov r0, #0 @ return 0
b END FAL:
mov r4, #0
mov r5, #0
mov r0, #-1 @ return -1
b SUC END:
pop {r4-r11} @ 还原现场
bx lr

3、JNI实现

asm_jni.cpp

#include "asm_jni.h"
#include <android/log.h> #define TAG "ASM_TEST" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) #ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jintArray JNICALL
Java_com_kryo_asm_TowSumAsm_towsum(JNIEnv *env, jobject thiz, jintArray data, jint target) { jintArray r_array = env->NewIntArray(2);
jint *elements_out = env->GetIntArrayElements(r_array, NULL); jsize length = env->GetArrayLength(data);
jint *elements_in = env->GetIntArrayElements(data, NULL); #ifdef USE_ASM
LOGD("call tow_sum_asm !\n");
tow_sum_asm(elements_in, elements_out, (size_t) length, 2, (size_t) target);
#else
LOGD("call tow_sum_cpp !\n");
tow_sum_cpp(elements_in, elements_out, (size_t) length, (size_t) target);
#endif env->ReleaseIntArrayElements(data, elements_in, 0);
env->ReleaseIntArrayElements(r_array, elements_out, 0); return r_array;
}
#ifdef __cplusplus
}
#endif

TowSumAsm.java

public class TowSumAsm {
static {
System.loadLibrary("asm");
}
public native int[] towsum(int[] data, int target);
}

最后贴一下从webrtc开源代码中copy来的asm_defines.h

/*
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/ #ifndef KRYO_INCLUDE_ASM_DEFINES_H_
#define KRYO_INCLUDE_ASM_DEFINES_H_ #if defined(__linux__) && defined(__ELF__)
.section .note.GNU-stack,"",%progbits
#endif // Define the macros used in ARM assembly code, so that for Mac or iOS builds
// we add leading underscores for the function names.
#ifdef __APPLE__
.macro GLOBAL_FUNCTION name
.global _\name
.private_extern _\name
.endm
.macro DEFINE_FUNCTION name
_\name:
.endm
.macro CALL_FUNCTION name
bl _\name
.endm
.macro GLOBAL_LABEL name
.global _\name
.private_extern _\name
.endm
#else
.macro GLOBAL_FUNCTION name
.global \name
.hidden \name
.endm
.macro DEFINE_FUNCTION name
#if defined(__linux__) && defined(__ELF__)
.type \name,%function
#endif
\name:
.endm
.macro CALL_FUNCTION name
bl \name
.endm
.macro GLOBAL_LABEL name
.global \name
.hidden \name
.endm
#endif // With Apple's clang compiler, for instructions ldrb, strh, etc.,
// the condition code is after the width specifier. Here we define
// only the ones that are actually used in the assembly files.
#if (defined __llvm__) && (defined __APPLE__)
.macro streqh reg1, reg2, num
strheq \reg1, \reg2, \num
.endm
#endif
.text
#endif // KRYO_INCLUDE_ASM_DEFINES_H_

4、CMakeLists.txt编写生成libasm.so

CMakeLists.txt

cmake_minimum_required(VERSION 3.10.2)

project("asm")

ENABLE_LANGUAGE(ASM) #启用汇编支持

if(${ANDROID_ABI} STREQUAL "armeabi-v7a")
add_library(asm SHARED
asm_jni.cpp
tow_sum_armv7a.S)
add_definitions(-DUSE_ASM)
elseif(${ANDROID_ABI} STREQUAL "arm64-v8a")
add_library(asm SHARED
asm_jni.cpp
tow_sum_cpp.cpp)
else()
message(FATAL_ERROR "Unsupported ABI: ${ANDROID_ABI}")
endif() target_link_libraries(asm
log)

5、运行测试

TowSumAsm towSumAsm = new TowSumAsm();
int[] result = towSumAsm.towsum(new int[]{1, 3, 5, 7, 9}, 12);
Log.d(TAG, "result " + result[0] + " " + result[1]);
2024-04-05 10:24:29.269 19863-19863 ASM_TEST                com.kryo.demo                        D  call tow_sum_asm !
2024-04-05 10:24:29.269 19863-19863 JNI_Activity com.kryo.demo D result 1 4

Reference

Android NDK之使用 arm-v7a 汇编实现两数之和的更多相关文章

  1. 对于Android NDK编译器ARM和Thumb模式的理解

    编译NDK项目时,编译器无法识别arm汇编,设置LOCAL_ARM_MODE := arm后问题解决, NDK文档上对LOCAL_ARM_MODE的说明如下: LOCAL_ARM_MODE By de ...

  2. Android NDK开发之Android.mk文件

    Android NDK开发指南---Android.mk文件 博客分类: Android NDK开发指南   Android.mk文件语法详述 介绍: ------------ 这篇文档是用来描述你的 ...

  3. Android NDK开发Crash错误定位[转]

    使用 ndk-stack 的时候需要你的 lib 编译为 debug版的,通常需要下面的修改: 1. 修改 android.mk,增加,为 LOCAL_CFLAGS 增加 -g 选项 2. 修改 ap ...

  4. Android NDK开发入门实例

    AndroidNDK是能使Android应用开发者把从c/c++编译而来的本地代码嵌入到应用包中的一系列工具的组合. 注意: AndroidNDK只能用于Android1.5及以上版本中. I. An ...

  5. [原]如何用Android NDK编译FFmpeg

    我们知道在Ubuntu下直接编译FFmpeg是很简单的,主要是先执行./configure,接着执行make命令来编译,完了紧接着执行make install执行安装.那么如何使用Android的ND ...

  6. 下面就介绍下Android NDK的入门学习过程(转)

    为何要用到NDK? 概括来说主要分为以下几种情况: 1. 代码的保护,由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大. 2. 在NDK中调用第三方C/C++库,因为大部分的开源库 ...

  7. Android NDK 开发(四)java传递数据到C【转】

    转载请注明出处:http://blog.csdn.net/allen315410/article/details/41845701 前面几篇文章介绍了Android NDK开发的简单概念.常见错误及处 ...

  8. Android NDK 开发(二) -- 从Hlello World学起【转】

    转载请注明出处:http://blog.csdn.net/allen315410/article/details/41805719  上篇文章讲述了Android NDK开发的一些基本概念,以及NDK ...

  9. (转)Android: NDK编程入门笔记

    转自: http://www.cnblogs.com/hibraincol/archive/2011/05/30/2063847.html 为何要用到NDK? 概括来说主要分为以下几种情况: 1. 代 ...

  10. Android NDK环境配置

    之前做了一个基于ffmpeg的软解播放器,熟悉了NDK开发的配置环境过程,但是由于太忙一直没有时间写笔记. 首先,介绍一下在这里所参与协作的软件包: 1. JDK: 这个软件被Eclipse依赖. 2 ...

随机推荐

  1. django学习第十一天---django操作cookie和session

    Cookie cookie解析 会话 http协议是无状态的,无连接的 导致每次客户端访问服务端需要登录成功之后才能访问的页面,都需要用户再重新登录一遍,用户体验极差. 客户端想了个办法,cookie ...

  2. 如何拓展jwt返回的数据

    默认的返回值仅有token,我们还需在返回值中增加username和id,方便在客户端页面中显示当前登陆用户 通过修改该视图的返回值可以完成我们的需求. 在user/utils.py中,创建 def ...

  3. 2-Django之三板斧

    HttpResponse 返回字符串类型的数据 HttpResponse: 这是 Django 自带的类,用于构建基本的 HTTP 响应 我的app名称是demo,我们先按照正常的流程,在views中 ...

  4. STM32标准库内部Flash读写

    STM32标准库FLASH读写 1. STM32内部FLASH介绍 STM32系列一般集成有内部flash,这部分内存可以直接通过指针的形式进行读取.但是由于内部flash一般存储为重要数据或程序运行 ...

  5. STL-RBTree模拟实现

    #pragma once #include<assert.h> #include<iostream> using std::cout; using std::endl; usi ...

  6. C++ STL容器 set类型

    C++ STL容器 set类型 set是C++引入的二叉树数据结构 特点: 自动将元素排序 插入和删除查找logn 必须元素支持严格的弱顺序 不能改变元素的值 代码 using Group = std ...

  7. 2.Canal连接MQ

    1. 配置文件介绍 Canal的启动,是以创建实例(instance)的方式,每个实例都有自己单独的工作环境, 而配置也分成两个部分 canal.properties (系统根配置文件) instan ...

  8. SSH原理与实践(一)

    主页 个人微信公众号:密码应用技术实战 个人博客园首页:https://www.cnblogs.com/informatics/ 引言 在日常开发和运维中,我们时常需要通过SSH登录远程主机,进行一些 ...

  9. STM32标准库通用定时器输入捕获

    STM32标准库定时器输入捕获 1.输入捕获介绍 输入捕获为STM32定时器的一个功能,可以用来测量输入信号的频率和占空比. 具体原理:当输入信号经过比较捕获通道时,STM32会依据通道的极性设置决定 ...

  10. cglib FastClass机制

    前言 关于动态代理的一些知识,以及cglib与jdk动态代理的区别,在这一篇已经介绍过,不熟悉的可以先看下. 本篇我们来学习一下cglib的FastClass机制,这是cglib与jdk动态代理的一个 ...