优化Neon汇编函数

4

我正在开发一款原生Android应用程序,它应该在ARMv7处理器设备上运行。由于某些原因,我需要对向量(short和/或float)进行一些重型计算。我使用NEON命令实现了一些汇编函数来提高计算效率。我已经获得了1.5倍的速度因子,这还不错。我想知道是否可以进一步改进这些函数以获得更快的速度。

所以问题是:我可以做哪些改变来改进这些函数?

    //add to float vectors.
//the result could be put in scr1 instead of dst
void add_float_vector_with_neon3(float* dst, float* src1, float* src2, int count)
{

    asm volatile (
           "1:                                                        \n"
           "vld1.32         {q0}, [%[src1]]!                          \n"
           "vld1.32         {q1}, [%[src2]]!                          \n"
           "vadd.f32        q0, q0, q1                                \n"
           "subs            %[count], %[count], #4                    \n"
           "vst1.32         {q0}, [%[dst]]!                           \n"
           "bgt             1b                                        \n"
           : [dst] "+r" (dst)
           : [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count)
           : "memory", "q0", "q1"
      );
}

//multiply a float vector by a scalar.
//the result could be put in scr1 instead of dst
void mul_float_vector_by_scalar_with_neon3(float* dst, float* src1, float scalar, int count)
{

    asm volatile (

            "vdup.32         q1, %[scalar]                              \n"
            "2:                                                         \n"
            "vld1.32         {q0}, [%[src1]]!                           \n"
            "vmul.f32        q0, q0, q1                                 \n"
            "subs            %[count], %[count], #4                     \n"
            "vst1.32         {q0}, [%[dst]]!                            \n"
            "bgt             2b                                         \n"
            : [dst] "+r" (dst)
            : [src1] "r" (src1), [scalar] "r" (scalar), [count] "r" (count)
            : "memory", "q0", "q1"
      );
}

//add to short vector -> no problem of coding limits
//the result should be put in in a dest different from src1 and scr2
void add_short_vector_with_neon3(short* dst, short* src1, short* src2, int count)
{

    asm volatile (
           "3:                                                        \n"
           "vld1.16         {q0}, [%[src1]]!                          \n"
           "vld1.16         {q1}, [%[src2]]!                          \n"
           "vadd.i16        q0, q0, q1                                \n"
           "subs            %[count], %[count], #8                    \n"
           "vst1.16         {q0}, [%[dst]]!                           \n"
           "bgt             3b                                        \n"
           : [dst] "+r" (dst)
           : [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count)
           : "memory", "q0", "q1"
      );
}

//multiply a short vector by a float vector and put the result bach into a short vector
//the result should be put in in a dest different from src1
void mul_short_vector_by_float_vector_with_neon3(short* dst, short* src1, float* src2, int count)
{
    asm volatile (
        "4:                                                         \n"
        "vld1.16        {d0}, [%[src1]]!                            \n"
        "vld1.32        {q1}, [%[src2]]!                            \n"
        "vmovl.s16      q0, d0                                      \n"
        "vcvt.f32.s32   q0, q0                                      \n"
        "vmul.f32       q0, q0, q1                                  \n"
        "vcvt.s32.f32   q0, q0                                      \n"
        "vmovn.s32      d0, q0                                      \n"
        "subs            %[count], %[count], #4                     \n"
        "vst1.16         {d0}, [%[dst]]!                            \n"
        "bgt             4b                                         \n"
        : [dst] "+r" (dst)
        : [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count)
        : "memory", "d0", "q0", "q1"

    );
}

Thanks in advance !


1
这是汇编语言,不是内置函数。 - user3528438
谢谢,我已经修改了帖子。 - MadMax007
我在https://software.intel.com/en-us/blogs/2012/12/12/from-arm-neon-to-intel-mmxsse-automatic-porting-solution-tips-and-tricks上找到了大量有用的技巧。 - technosaurus
首要的原则是不要在加载后立即使用结果,因为加载需要时间并可能阻塞下一条指令。因此,您始终希望交错执行指令,或者将指令进行“软件流水线”处理。 - user3528438
我不会ARM语言。但是我有点担心这段代码,因为在文档中有这样一句话:“警告:不要修改仅限输入的操作数的内容(除了与输出绑定的输入)。 ”为了澄清这个限制,当寄存器包含“count”时,它退出asm时是否具有完全相同的值?如果答案是否定的,则违反了规则。如果gcc尝试重新使用它“知道”包含特定值的寄存器,却发现你误导了它,可能会导致糟糕的结果。 - David Wohlferd
3个回答

1
你可以尝试展开循环以处理更多的元素。
您的 add_float_vector_with_neon3 代码每4个元素需要10个周期(因为出现了停顿),而展开到16个元素则需要21个周期。 http://pulsar.webshaker.net/ccc/sample-34e5f701 虽然存在一些开销,因为您需要处理余数(或者您可以将数据填充为16的倍数),但如果您有大量的数据,则与实际总和相比,开销应该相当低。

嗨,这是一个不错的回复!既有指向http://pulsar.webshaker.net/ccc/index.php的工具,也有提出的技术。我没有剩余(每个向量512个值),但需要添加许多向量。 - MadMax007

0

这是一个关于如何使用NEON指令编写代码的示例。

优点在于,您可以使用编译器来优化寄存器分配和指令调度,同时限制指令使用。

缺点是,GCC似乎无法将指针算术运算合并到加载/存储指令中,因此需要发出额外的ALU指令来执行。或者我可能错了,GCC以这种方式执行有充分的理由。

使用GCC和CFLAGS=-std=gnu11 -O3 -fgcse-lm -fgcse-sm -fgcse-las -fgcse-after-reload -mcpu=cortex-a9 -mfloat-abi=hard -mfpu=neon -fPIE -Wall,此代码编译为非常不错的目标代码。循环展开并交错以隐藏加载结果可用之前的长延迟。而且它也很易读。

#include <arm_neon.h>

#define ASSUME_ALIGNED_FLOAT_128(ptr) ((float *)__builtin_assume_aligned((ptr), 16))

__attribute__((optimize("unroll-loops")))
void add_float_vector_with_neon3(      float *restrict dst,
                                 const float *restrict src1,
                                 const float *restrict src2, 
                                 size_t size)
{
    for(int i=0;i<size;i+=4){
        float32x4_t inFloat41  = vld1q_f32(ASSUME_ALIGNED_FLOAT_128(src1));
        float32x4_t inFloat42  = vld1q_f32(ASSUME_ALIGNED_FLOAT_128(src2));
        float32x4_t outFloat64 = vaddq_f32 (inFloat41, inFloat42);
        vst1q_f32 (ASSUME_ALIGNED_FLOAT_128(dst), outFloat64);
        src1+=4;
        src2+=4;
        dst+=4;
    }
}

谢谢!我将比较这两个函数(汇编 VS 内嵌函数)。我会反馈它们的性能表现。 - MadMax007
很不幸,我的代码在使用内置函数时出现了编译问题。我收到了一个“vld1q_f32无法解析”的错误。 - MadMax007
1
@MadMax007 如果你已经会写汇编语言,那么Intrinsics不值得你花时间去学习。只需要展开循环,性能将会提高一倍以上。 - Jake 'Alquimista' LEE

0

好的,我比较了初始帖子中给出的代码和Josejulio提出的新函数:

void add_float_vector_with_neon3(float* dst, float* src1, float* src2, int count)
{
    asm volatile (
            "1:                                 \n"
            "vld1.32 {q0,q1}, [%[src1]]!        \n"
            "vld1.32 {q2,q3}, [%[src2]]!        \n"
            "vadd.f32 q0, q0, q2                \n"
            "vadd.f32 q1, q1, q3                \n"
            "vld1.32 {q4,q5}, [%[src1]]!        \n"
            "vld1.32 {q6,q7}, [%[src2]]!        \n"
            "vadd.f32 q4, q4, q6                \n"
            "vadd.f32 q5, q5, q7                \n"
            "subs %[count], %[count], #16       \n"
            "vst1.32 {q0, q1}, [%[dst]]!        \n"
            "vst1.32 {q4, q5}, [%[dst]]!        \n"
            "bgt             1b                 \n"
            : [dst] "+r" (dst)
            : [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count)
            : "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7"
      );
}

而在工具中(pulsar.webshaker.net/ccc/index.php),CPU循环/浮点数有很大的差异,但我没有看到延迟检查方面有太大的差异:

中位数、第一四分位数、第三四分位数、最小值、最大值(微秒,1000次测量)

原始数据:3564、3206、5126、1761、12144

展开后:3567、3080、4877、3018、11683

因此,我不确定展开是否如此高效...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接