示例:矢量加法
让我们看看如何使用编译器选项自动矢量化和优化简单的C程序。
1、创建一个包含以下功能的新文件vec_add.c。 此函数将两个32位浮点值数组相加。
void vec_add(float *vec_A, float *vec_B, float *vec_C, int len_vec) {
int i;
for (i=0; i<len_vec; i++) {
vec_C[i] = vec_A[i] + vec_B[i];
}
}
编译代码,不使用自动向量化:
armclang --target=aarch64-arm-none-eabi -g -c -O1 vec_add.c
反汇编生成的目标文件以查看生成的指令:
fromelf --disassemble vec_add.o -o disassembly_vec_on.txt
反汇编的代码类似于以下内容:
vec_add ; Alternate entry point
CMP w3,#1
B.LT |L3.36|
MOV w8,w3
|L3.12|
LDR s0,[x0],#4
LDR s1,[x1],#4
SUBS x8,x8,#1
FADD s0,s0,s1
STR s0,[x2],#4
B.NE |L3.12|
|L3.36|
RET
在这里,我们可以看到该函数的标签名称vec_add,后面是组成该函数的生成的汇编指令。 FADD指令执行操作的核心部分,但是由于一次仅执行一次加法操作,因此代码未使用Neon。 我们可以看到这是因为FADD指令正在标量寄存器S0和S1上运行。
重新编译代码,这次使用自动向量化:
armclang --target=aarch64-arm-none-eabi -g -c -O1 vec_add.c -fvectorize
反汇编生成的目标文件以查看生成的指令:
fromelf --disassemble vec_add.o -o disassembly_vec_on.txt
反汇编的代码类似于以下内容:
vec_add ; Alternate entry point
CMP w3,#1
B.LT |L3.184|
CMP w3,#4
MOV w8,w3
MOV x9,xzr
B.CC |L3.140|
LSL x10,x8,#2
ADD x12,x0,x10
ADD x11,x2,x10
CMP x12,x2
ADD x10,x1,x10
CSET w12,HI
CMP x11,x0
CSET w13,HI
CMP x10,x2
CSET w10,HI
CMP x11,x1
AND w12,w12,w13
CSET w11,HI
TBNZ w12,#0,|L3.140|
AND w10,w10,w11
TBNZ w10,#0,|L3.140|
AND x9,x8,#0xfffffffc
MOV x10,x9
MOV x11,x2
MOV x12,x1
MOV x13,x0
|L3.108|
LDR q0,[x13],#0x10
LDR q1,[x12],#0x10
SUBS x10,x10,#4
FADD v0.4S,v0.4S,v1.4S
STR q0,[x11],#0x10
B.NE |L3.108|
CMP x9,x8
B.EQ |L3.184|
|L3.140|
LSL x12,x9,#2
ADD x10,x2,x12
ADD x11,x1,x12
ADD x12,x0,x12
SUB x8,x8,x9
|L3.160|
LDR s0,[x12],#4
LDR s1,[x11],#4
SUBS x8,x8,#1
FADD s0,s0,s1
STR s0,[x10],#4
B.NE |L3.160|
|L3.184|
RET
从指令FADD v0.4S,v0.4S,v1.4S可以看出,SLP自动矢量化已成功完成,该指令对打包到SIMD寄存器中的四个32位浮点数执行加法运算。 但是,这给代码大小带来了巨大的代价,因为它必须检测SIMD宽度不是数组长度的因数的情况。 取决于项目和目标硬件,这样的代码大小增加可能是可接受的,也可能不是。 对于代码大小的变化与可用内存相比微不足道的电话应用程序,这可能是可以容忍的,但是对于具有少量RAM的嵌入式应用程序,这是不可接受的。