示例:循环功能
如果要使用编译器的特定优化功能,有时不可避免地需要更改源代码。 当代码过于复杂以至于编译器无法自动矢量化时,或者当您想要覆盖编译器有关如何优化特定代码的决定时,就会发生这种情况。
创建一个包含以下功能的新文件cubed.c。 此函数计算值数组的多维数据集。
double cubed(double x) { return x*x*x; } void vec_cubed(double *x_vec, double *y_vec, int len_vec) { int i; for (i=0; i使用自动向量化来编译代码:
armclang --target=aarch64-arm-none-eabi -g -c -O1 -fvectorize cubed.c反编译生成的目标文件以查看生成的指令:
fromelf --disassemble cubed.o -o disassembly.txt反汇编的代码类似于以下内容:
cubed ; Alternate entry point FMUL d1,d0,d0 FMUL d0,d1,d0 RET AREA ||.text.vec_cubed||, CODE, READONLY, ALIGN=2 vec_cubed ; Alternate entry point STP x21,x20,[sp,#-0x20]! STP x19,x30,[sp,#0x10] CMP w2,#1 B.LT |L4.48| MOV x19,x1 MOV x20,x0 MOV w21,w2 |L4.28| LDR d0,[x20],#8 BL cubed SUBS x21,x21,#1 STR d0,[x19],#8 B.NE |L4.28| |L4.48| LDP x19,x30,[sp,#0x10] LDP x21,x20,[sp],#0x20 RET此代码中存在许多问题:
编译器尚未执行循环或SLP矢量化,或未内联我们的多维数据集函数。
该代码需要对输入指针执行检查以验证数组不重叠。
可以通过多种方式解决这些问题,例如以更高的优化级别进行编译,但是让我们集中讨论可以在不更改编译器选项的情况下进行哪些代码更改。
将以下宏和限定符添加到代码中,以覆盖某些编译器的决策。
__attribute __((always_inline))是Arm编译器扩展,它指示编译器始终尝试内联函数。 在此示例中,不仅内联函数,而且编译器还可以执行SLP矢量化。
在进行内联之前,多维数据集函数仅适用于标量双精度,因此不需要或没有必要自行对此函数执行SLP矢量化。内联立方体函数时,编译器可以检测到其操作是在数组上执行的,并使用可用的ASIMD指令对代码进行向量化。
limit是一个标准的C / C ++关键字,它向编译器指示给定的数组对应于内存的唯一区域。 这消除了对重叠数组进行运行时检查的需要。
#pragma clang loop interleave_count(X)是Clang语言扩展,可让您通过指定矢量宽度和交织计数来控制自动矢量化。 此编译指示是Arm Compiler的[COMMUNITY]功能。
可以在clang文档中找到对矢量化宏的完整参考。__always_inline double cubed(double x) { return x*x*x; } void vec_cubed(double *restrict x_vec, double *restrict y_vec, int len_vec) { int i; #pragma clang loop interleave_count(2) for (i=0; i
使用我们之前使用的相同命令进行编译和反汇编。 这将产生以下代码:
vec_cubed ; Alternate entry point CMP w2,#1 B.LT |L4.132| CMP w2,#4 MOV w8,w2 B.CS |L4.28| MOV x9,xzr B |L4.92| |L4.28| AND x9,x8,#0xfffffffc ADD x10,x0,#0x10 ADD x11,x1,#0x10 MOV x12,x9 |L4.44| LDP q0,q1,[x10,#-0x10] ADD x10,x10,#0x20 SUBS x12,x12,#4 FMUL v2.2D,v0.2D,v0.2D FMUL v3.2D,v1.2D,v1.2D FMUL v0.2D,v0.2D,v2.2D FMUL v1.2D,v1.2D,v3.2D STP q0,q1,[x11,#-0x10] ADD x11,x11,#0x20 B.NE |L4.44| CMP x9,x8 B.EQ |L4.132| |L4.92| LSL x11,x9,#3 ADD x10,x1,x11 ADD x11,x0,x11 SUB x8,x8,x9 |L4.108| LDR d0,[x11],#8 SUBS x8,x8,#1 FMUL d1,d0,d0 FMUL d0,d0,d1 STR d0,[x10],#8 B.NE |L4.108| |L4.132| RET
此反汇编表明内联,SLP矢量化和循环矢量化已成功。 使用限制指针消除了运行时重叠检查。
当循环总计数不是四的倍数(有效展开深度)时,由于循环尾可以处理任何剩余的迭代,因此代码大小略有增加。 循环展开深度为2,SLP宽度为2,因此有效展开深度为4。 在下一步中,如果我们知道循环计数始终是4的倍数,我们将进行优化。
让我们假设循环计数始终是四的倍数。 我们可以通过屏蔽循环计数器的低位与编译器进行通信:
void vec_cubed(double *restrict x_vec, double *restrict y_vec, int len_vec) { int i; #pragma clang loop interleave_count(1) for (i=0; i<(len_vec & ~3); i++) { y_vec[i] = cubed_i(x_vec[i]); } }使用我们之前使用的相同命令进行编译和反汇编。 这将产生以下代码:
vec_cubed ; Alternate entry point AND w8,w2,#0xfffffffc CMP w8,#1 B.LT |L13.40| MOV w8,w8 |L13.16| LDR q0,[x0],#0x10 SUBS x8,x8,#2 FMUL v1.2D,v0.2D,v0.2D FMUL v0.2D,v0.2D,v1.2D STR q0,[x1],#0x10 B.NE |L13.16| |L13.40| RET由于编译器知道不再需要测试和处理任何不是四倍数的其余迭代,因此代码量得以减少。 向编译器承诺,我们提供的数据将始终是向量长度的倍数,从而产生了优化的代码。
该示例非常简单,以-O2进行编译将在不更改代码的情况下执行所有这些优化,但是更复杂的代码段可能需要这种类型的调整才能从编译器中获得最大收益。
下面包括完整的代码清单。 您可以在各种优化级别和展开深度进行编译和反汇编,以观察编译器的自动矢量化行为。
/* * Copyright (C) Arm Limited, 2019 All rights reserved. * * The example code is provided to you as an aid to learning when working * with Arm-based technology, including but not limited to programming tutorials. * Arm hereby grants to you, subject to the terms and conditions of this Licence, * a non-exclusive, non-transferable, non-sub-licensable, free-of-charge licence, * to use and copy the Software solely for the purpose of demonstration and * evaluation. * * You accept that the Software has not been tested by Arm therefore the Software * is provided "as is", without warranty of any kind, express or implied. In no * event shall the authors or copyright holders be liable for any claim, damages * or other liability, whether in action or contract, tort or otherwise, arising * from, out of or in connection with the Software or the use of Software. */ #includevoid vec_init(double *vec, int len_vec, double init_val) { int i; for (i=0; i