【问题标题】:Optimizing neon assembly functions优化霓虹灯组装功能
【发布时间】:2016-01-30 20:09:41
【问题描述】:

我正在开发一个应该在 ARMv7 处理器设备上运行的原生 android 应用程序。 由于某些原因,我需要对向量(短和/或浮点)进行一些繁重的计算。 我使用 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"

    );
}

提前致谢!

【问题讨论】:

  • 嗯,这是程序集而不是内在函数
  • 谢谢,我改了帖子
  • 我在software.intel.com/en-us/blogs/2012/12/12/… 找到了很多有用的提示
  • 第一条经验法则是不要尝试在加载后立即使用加载的结果,因为加载需要时间并且可能会停止下一条指令。所以你总是想交错指令,或者software-pipeline指令。
  • 我不会说 ARM。完全没有。但由于docs:Warning: Do not modify the contents of input-only operands (except for inputs tied to outputs) 中的这一行,我确实有点担心这段代码。为了澄清这个限制,包含“count”的寄存器在退出 asm 时的值是否与进入时的值完全相同?如果答案是否定的,你就违反了规则。如果 gcc 尝试重新使用它“知道”包含特定值的寄存器,却发现你误导了它,则可能会导致坏事。

标签: android c native inline-assembly neon


【解决方案1】:

您可以尝试展开循环以在每个循环中处理更多元素。

您的 add_float_vector_with_neon3 代码每 4 个元素需要 10 个周期(因为停顿),而展开到 16 个元素需要 21 个周期。 http://pulsar.webshaker.net/ccc/sample-34e5f701

虽然存在开销,因为您需要处理余数(或者您可以将数据填充为 16 的倍数),但如果您有大量数据,与实际总和相比,开销应该相当低。

【讨论】:

  • 嗨,这是一个很好的回复! pulsar.webshaker.net/ccc/index.php 指出的工具和提出的技术。我没有余数(每个向量 512 个值),但要添加许多向量。
【解决方案2】:

这是一个关于如何使用霓虹灯内部编码的示例。

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

缺点是,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 内在函数)。我会就表演给予反馈。
  • 不幸的是,我在代码中使用内部函数时遇到了编译问题。我收到“vld1q_f32 无法解析”。
  • @MadMax007 如果您已经可以编写汇编代码,那么内在函数就不值得您花时间。只需展开你的循环,性能就会增加一倍以上。
【解决方案3】:

好的,我比较了最初帖子中给出的代码和 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 周期/浮点数有很大差异,我看不出延迟检查有太大差异:

median, firstQuartile, thirdQuartile, minVal, maxVal(微秒,1000 次测量)

原件:3564、3206、5126、1761、12144

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

所以我不确定展开是否有效率......

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-01
    • 2014-07-30
    • 1970-01-01
    • 1970-01-01
    • 2011-08-08
    • 1970-01-01
    相关资源
    最近更新 更多