CPU架构之ARM优化

版权声明:本文为博主原创文章,转载请标注转载网址:http://blog.csdn.net/frd2009041510 https://blog.csdn.net/FRD2009041510/article/details/80803240

1、资源

1.1、ARM开发者官网

https://developer.arm.com/

http://infocenter.arm.com

1.2、纯汇编和inline汇编参考资源

    32位ARM优化可以参考文档:

https://developer.arm.com/products/architecture/a-profile/docs/ddi0406/latest/arm-architecture-reference-manual-armv7-a-and-armv7-r-edition

    64位ARM优化可以参考文档:

https://developer.arm.com/products/architecture/a-profile/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile

    其他资源(均来源于http://infocenter.arm.com):

RealView编译工具《编译器参考指南》:

http://infocenter.arm.com/help/topic/com.arm.doc.dui0348bc/DUI0348BC_rvct_comp_ref_guide.pdf

RealView编译工具《汇编器指南》:

http://infocenter.arm.com/help/topic/com.arm.doc.dui0204ic/DUI0204IC_rvct_assembler_guide.pdf

RealView编译工具《链接器用户指南》:

http://infocenter.arm.com/help/topic/com.arm.doc.dui0206ic/DUI0206IC_rvct_linker_user_guide.pdf

1.3、Neon Intrinsics参考资源

https://developer.arm.com/technologies/neon/intrinsics

http://infocenter.arm.com/help/topic/com.arm.doc.ihi0073a/IHI0073A_arm_neon_intrinsics_ref.pdf

http://infocenter.arm.com/help/topic/com.arm.doc.ihi0073b/IHI0073B_arm_neon_intrinsics_ref.pdf

2、介绍

2.1、ARM优化简介

    ARM是三大CPU架构(X86、ARM、MIPS)之一,是精简指令集计算机的代表,其功耗低、功能强的特点使之广泛应用于移动设备。目前常见的是ARM32和ARM64,下面对其分别简单介绍:

(1)ARM32以ARMv7为主要架构,是32位的,常见的设备有:iphone5、Cortex-A15;

(2)ARM64以ARMv8为主要架构,是64位的,常见的设备有:Cortex-A53、Cortex-A57、iphone5s的A7、iphone6和iphone6Plus的A8等。

2.2、ARM指令集简介

    Thumb指令、ARM指令和NEON指令的区别和联系:Thumb指令是ARM指令中一种16位的指令集,具有16bit的代码密度;ARM指令集包含ARM32和ARM64,分别适用于ARM32位优化和ARM64位优化;NEON指令集同样包含NEON32和NEON64,分别适用于ARM32位优化和ARM64位优化。不管ARM指令集还是NEON指令集,虽然都包含适用于ARM32位优化和ARM64位优化的指令集,但是它们的指令名称、个数和使用方法均是不一样的,需要特别注意,尤其是在ARM32位优化改写为ARM64位优化时。

2.3、callee和caller简介

3、ARM32位优化

3.1、ARM32位寄存器

    ARM32位寄存器共有16个(R0~R15),均是32位宽的,ARM32遵循ATPCS(ARM-THUMB procedure call standard,ARM-Thumb过程调用标准)规则:

        a. R0~R3寄存器用于子程序间传递参数,当参数大于4时,将多余的参数用数据栈进行传递,入栈的顺序与参数顺序恰好相反,被调用子程序返回前无需恢复寄存器R0~R3的值,也就是说R0~R3无需出栈和入栈;

        b. R4~R11寄存器用于子程序间保存变量,这些寄存器在子程序进入时必须保存,返回时必须恢复,也就是说R4~R11使用时必须出栈和入栈;

        c. R13是堆栈指针寄存器SP(在进入子程序时的值和退出子程序时的值必须相等),R14是链接寄存器LR,R15是一个程序计数器PC;

        d. R12时一般的通用寄存器,使用时不需要保存;

        e. 子程序返回32位的整数,使用R0返回;返回64位的整数时,使用R0返回低位、R1返回高位。

3.2、NEON32位寄存器

    NEON32位寄存器主要包含如下寄存器,由于NEON寄存器是有重叠部分物理地址的,所以使用时需要特别注意:

32个单字(32bits)的S寄存器(S0~S31);

32个双字(64bits)的D寄存器(D0~D31);

16个四字(128bits)的Q寄存器(Q0~Q15)。

    neon的S/D/Q寄存器之间的关系举例如下:

S0 S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12 S13 S14 S15
D0 D1 D2 D3 D4 D5 D6 D7
Q0 Q1 Q2 Q3

    在使用neon32位寄存器时,如果用到d8~d15寄存器,需要先入栈vpush {d8~d15},使用完后要出栈vpop {d8~d15};若对应于S寄存器,则是S16~S31;对应于Q寄存器,则是Q4~Q7。

4、ARM32位优化模板

    假设我们有如下三个文件:

arm32_opt_c.c        :待优化的c源码和demo模板

arm32_opt_arm.s    :arm32汇编优化源码

makefile                  :优化模板的makefile,编译出可执行文件测试出汇编性能提升倍数

arm32_opt_c.c内容如下:

#include <string.h>
#incldue <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>

extern void arm32_opt_arm(unsigned short *output_asm,unsigned short *input,int width,int height,int blocksize,int stride);

void arm32_opt_c(unsigned short *output_c,unsigned short *input,int width,int height,int blocksize);
{
    int x,y;
    for(x=0;x<width;x++)
    {
        output_c[0*width+x]=0;
        for(y=0;y<blocksize;y++)
            output_c[0*width+x] += input[y*width+x]
    }
}

int main(int argc,char *argv[])
{
    int m,loop_num=10000;
    int i,j;
    clock_t BeginTime1,EndTime1,BeginTime2,EndTime2;
    int width,height,blocksize;
    width=atoi(argv[1]);
    height=atoi(argv[2]);
    blcoksize=atoi(argv[3]);

    srand((int)time(0));

    unsigned short *input=NULL;
    unsigned short *output_c=NULL;
    unsigned short *output_asm=NULL;
    input=(unsigned short *)malloc(width*height*sizeof(unsigned short));
    output_c=(unsigned short *)malloc(width*height*sizeof(unsigned short));
    output_asm=(unsigned short *)malloc(width*height*sizeof(unsigned short));
  
    if(input==NULL||output_c==NULL||output_asm=NULL)
    {
        return -1;
    }

    // 初始化输入和输出为0
    memset(input,0,width*height);
    memset(output_c,0,width*height);
    memset(output_asm,0,width*height);

    // 输入随机
    for(i=0;i<height;i++){ 
        for(j=0;j<width;j++){ 
            input[i*width+j]=rand()%0xFF; } }

    // arm优化
    BeginTime1=clock();
    for(m=0;m<loop_num;m++){ 
        arm32_opt_asm(output_asm,input,width,height,blocksize,width); }
    EndTime1=clock();
    printf("now :%6.3fms\n",(double)(EndTime1-BeginTime1));

    // c源码
    BeginTime2=clock();
    for(m=0;m<loop_num;m++){ 
        arm32_opt_c(output_asm,input,width,height,blocksize); }
    EndTime2=clock();
    printf("prev :%6.3fms\n",(double)(EndTime2-BeginTime2));

    // 打印优化性能提升结果
    printf("cycle_times = %d\n",loop_num);
    printf("perfo_impro:%f\n",(double)(EndTime2-BeginTime2)/(EndTime1-BeginTime1));

    // 验证优化结果是否正确
    for(i=0;i<height;i++){ 
        for(j=0;j<width;j++){ 
            if(output_c[i*width+j]!=output_asm[i*width+j]){ 
                printf("error_data[%d,%d]:%d,%d\n",j,i,output_c[i*width+j],output_asm[i*width+j]); } } }

    printf("Opt success!!!\n");

    free(input);
    free(output_c);
    free(output_asm);

    return 0;
}

arm32_opt_arm.s内容如下:

    .text
    .arm
    .align 4

.global arm32_opt_asm
arm32_opt_asm:

    PUSH {R4-R12,LR}    @ 入栈
    ADD R11,SP,#40      
    LDM R11,{R4,R5}     @ 加载多个寄存器 blcoksize stride

    MOV R8,R0           @ output_asm
    MOV R9,R1           @ input
    MOV R6,R2           @ width
loop_x:
    MOV R7,R4
    MOV R10,R9
    VEOR Q1,Q1          @ 按位异或

loop_y:
    VLD1.16 {Q1},[R10]  @ 向量加载
    VADD.U16 Q1,Q0,Q1   @ 向量加法
    ADD R10,R5,LSL#1
    SUBS R7,#1
    BGT loop_y          @ branch greater than

    VST1.16 {Q1},[R8]!  @ 向量存储
    ADD R9,#16
    SUBS R6,#8
    BGT loop_x

    POP {R4-R12,PC}
.end

makefile内容如下:

ROOTSRC = .
PLATFORM ?= 666888

ifeq ($(PLATFORM),666888)
CORSS = arm-hisiv500-linux-
CFLAGS += -march=armv7-a -mfloat-abi=softfp
SFLAGS += -march=armv7-a -mfpu=neon
endif

CC = $(CROSS)gcc
CPP = $(CROSS)g++
LD = $(CROSS)ld
AR = $(CROSS)ar
AS = $(CROSS)as

CFALGS += -O3
CFLAGS += -I$(ROOTSRC)

TARGET_APP = test
OBJS = $(ROOTSRC)/arm32_opt_c.o \
       $(ROOTSRC)/arm32_opt_asm.o

all:$(TARGET_APP)
$(TARGET_APP):$(OBJS)
    $(CC) -O $@ $(OBJS)
    rm -f $(OBJS)
    rm -f *.o

%.o:%.c
    $(CC) -c $< $(CFLAGS) -o $@

%.o:%.cpp
    $(CPP) -c $< $(CFLAGS) -o $@

%.o:%.S
    $(CC) -c $< $(CFLAGS) -o $@

%.o:%.s
    $(AS) -c $< $(SFLAGS) -o $@

clean:
    rm -rf $(OBJS)
    rm -rf $(TARGET_APP)

5、ARM64位优化

5.1、ARM64位寄存器

    ARM64有32个寄存器(X0~X31),均是64位宽的,ARM64遵循AAPCS64(ARM Archtecture Procedure Call Standard)规则,其中:

    a. 其中w0~w30分别是x0~x30的低32位;

    b. x0~x7寄存器用于子程序间传递参数,当参数大于8时,将多余的参数用数据栈进行传递,入栈的顺序与参数顺序恰好相反,被调用子程序返回前无需恢复寄存器x0~x7的值

    c. x8是XR,用于保存子程序返回地址;

    d. x9~x15是临时寄存器,使用时不需要保存;

    e. x16(IP0)、x17(IP1)是子程序内部调用寄存器,使用时不需要保存;

    f. x18(PR)是平台寄存器,它的使用和平台有关;

    g. x19~x28是临时寄存器,使用必须保存(入栈);

    h. x29(FP)是帧指针寄存器,用于链接栈帧,使用时需要保存;

    i. x30是链接寄存器LR,x31是堆栈指针寄存器SP;

    j. PC是程序寄存器。

5.2、NEON64位寄存器

Neon64寄存器主要包含如下:

32个B寄存器(B0~B31),8bit;

32个H寄存器(H0~H31),16bit;

32个S寄存器(S0~S31),单字(32bit);

32个D寄存器(D0~D31),双字(64bit);

32个Q寄存器(V0~V31),四字(128bit)。

它们之间的关系举例如下:

S0是32位的,是D0(64位)的低半部分,D0是V0(128位)的低半部分;S1是32位的,是D1(64位)的低半部分,D1是V1(128位)的低半部分,这一点与NEON32是不同的,需要注意!!!

除此之外,仍需要特别注意的是NEON寄存器v0~v31的使用方法:

    a. v0~v7:用于参数传递和返回值,callee(被调用者或者子程序)时不需要保存,即不需要出栈和入栈;

    b. v8~v15:callee子程序调用时必须入栈保存(低64位进行保存即可);

    c. v16~v31:callee不需要保存;

    d. caller可能需要保存的是v0~v7,v16~v31。

6、ARM64位优化模板

    假设我们有如下三个文件:

arm64_opt_c.c        :待优化的c源码和demo模板

arm64_opt_arm.s    :arm64汇编优化源码

makefile                  :优化模板的makefile,编译出可执行文件测试出汇编性能提升倍数

arm64_opt_c.c与arm32_opt_c.c一样;

arm64_opt_arm.s 如下:

    .text
    .align 4

.global arm64_opt_asm
arm64_opt_asm:

    

    mov x9,x0            // output_asm
    mov x10,x1           // input
    mov x11,x2           // width
loop_x:
    mov x12,x4
    mov x13,x10
    eor v1.16B,v1.16B,v1.16B  // 按位异或

loop_y:
    ldl {v0.8H},[x13]         // 向量加载
    add v1.8H,v0.8H,v1.8H     // 向量加法
    add x13,x13,x5,lsl #1
    subs x12,x12,#1
    bgt loop_y                // branch greater than

    st1 {v1.8H},[x9],#16      // 向量存储
    add x10,x10,#16
    subs x11,x11,#8
    bgt loop_x

    ret                       // 汇编调用的子程序返回

makefile与ARM32的异同点如下:

CORSS = aarch64-himix100-linux-
CFLAGS += -march=armv8-a -mfpu=cortex-a73
SFLAGS += -march=armv8-a -mfpu=cortex-a73

或者

CORSS = aarch64-himix100v630-linux-
CFLAGS += -march=armv8-a
SFLAGS += -march=armv8-a

7、ARM的inline汇编

    ARM32位的inline汇编与ARM64位的inline汇编格式一样,唯一不同的是里面使用的寄存器等,这也是ARM32位纯汇编和ARM64纯汇编的区别。除此之外,Inline汇编是由编译器管理入栈和出栈,而纯汇编则是由被调用者(子函数)管理寄存器入栈和出栈。还需注意的一点是汇编分“AT&T”和“Intel”格式。

    模板如下:

__asm volatile
(
    "汇编指令1\n\t"
    "汇编指令1\n\t"
    "汇编指令1\n\t"

    :输出变量列表【可选】
    :输入变量列表【可选】
    :被破坏的寄存器列表【可选】
);

举例如下:

__asm volatile
(
    "mov r0,%[dst]    \n\t"
    "mov r1,%[src]    \n\t"
    "mov r2,%[stride] \n\t"
    "mov r3,#16       \n\t"
    "指令              \n\t"
    :
    :
    [dst] "r" (dst),
    [src] "r" (src),
    [stride] "r" (stride)
    :"r0","r1","r2","memory"
);

8、ARM的intrinsic汇编

    Intrinsic作为内联函数,直接在调用的地方插入代码,即避免了函数调用的额外开销,又能够使用比较高级的机器指令对函数进行优化。

    需要说明的是:intrinsics下,arm32和aarch64下的代码是一致的。

9、ARM32位优化与ARM64位优化的区别与联系

(1)无论是ARM32位优化还是ARM64位优化,均可混合使用ARM寄存器和NEON寄存器;

(2)ARM32位优化的注释方法为“@”,而ARM64位优化的注释方法为“//”或者“/**/”。

10、ARM指令集在linux/ios/android下的区别

后续补充。。。

猜你喜欢

转载自blog.csdn.net/FRD2009041510/article/details/80803240