NEON_10

示例:循环功能
如果要使用编译器的特定优化功能,有时不可避免地需要更改源代码。 当代码过于复杂以至于编译器无法自动矢量化时,或者当您想要覆盖编译器有关如何优化特定代码的决定时,就会发生这种情况。

 

创建一个包含以下功能的新文件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.
 */

#include 
 
void vec_init(double *vec, int len_vec, double init_val) {
        int i;
        for (i=0; i 
 

你可能感兴趣的:(NEON)