ARM NEON指令集优化理论与实践

一.简介

NEON就是一种基于SIMD思想的ARM技术,相比于ARMv6或之前的架构,NEON结合了64-bit和128-bit的SIMD指令集,提供128-bit宽的向量运算(vector operations)。NEON技术从ARMv7开始被采用,目前可以在ARM Cortex-A和Cortex-R系列处理器中采用。NEON在Cortex-A7、Cortex-A12、Cortex-A15处理器中被设置为默认选项,但是在其余的ARMv7 Cortex-A系列中是可选项。NEON与VFP共享了同样的寄存器,但它具有自己独立的执行流水线。

二. NEON寄存器

三. NEON指令集

所有的支持NEON指令都有一个助记符V,下面以32位指令为例,说明指令的一般格式:

V{<mod>}<op>{<shape>}{<cond>}{.<dt>}{<dest>},
src1, src2

  • <mod>
    • Q: The instruction uses
      saturating arithmetic, so that the result is saturated within the range
      of the specified data type, such as VQABS, VQSHL etc.
    • H: The instruction will
      halve the result. It does this by shifting right by one place
      (effectively a divide by two with truncation), such as VHADD, VHSUB.
    • D: The instruction doubles
      the result, such as VQDMULL, VQDMLAL, VQDMLSL and VQ{R}DMULH.
    • R: The instruction will
      perform rounding on the result, equivalent to adding 0.5 to the result
      before truncating, such as VRHADD, VRSHR.
  • <op> - the operation (for example, ADD, SUB, MUL).
  • <shape> - Shape,即前文中的Long
    (L), Wide (W), Narrow (N).
  • <cond> - Condition, used with IT instruction.
  • <.dt> - Data type, such as s8, u8, f32 etc.
  • <dest> - Destination.
  • <src1> - Source operand 1.
  • <src2> - Source operand 2.

注: {} 表示可选的参数。

比如:

VADD.I16
D0, D1, D2   @ 16位加法

VMLAL.S16
Q2, D8, D9  @ 有符号16位乘加

四.NEON支持的指令总结

  1. 运算:和、差、积、商
  2. 共享的 NEON 和 VFP 指令:涉及加载、多寄存器间的传送、存储

五.  NEON 优化技术

在利用NEON优化程序时,有下述几项比较通用的优化技巧。

1. 降低数据依赖性

在ARM v7-A NEON指令通常需要3~9个指令周期,NEON指令比ARM指令需要更多周期数。因此,为了减少指令延时,最好避免将当前指令的目的寄存器当作下条指令的源寄存器。如下例所示:

/***************************************************************/

// C代码

float SumSquareError_C(const float* src_a, const float*
src_b, int count)

{

float sse = 0u;

int i;

for (i = 0; i
< count; ++i) {

float diff =
src_a[i] - src_b[i];

sse +=
(float)(diff * diff);

}

return sse;

}

// NEON实现一

float SumSquareError_NEON1(const float* src_a, const
float* src_b, int count)

{

float sse;

asm volatile (

"veor    q8, q8, q8                        \n"

"veor    q9, q9, q9                        \n"

"veor    q10, q10, q10                     \n"

"veor    q11, q11, q11                     \n"

"1:                                         
\n"

"vld1.32     {q0, q1},
[%0]!               \n"

"vld1.32     {q2, q3},
[%0]!               \n"

"vld1.32     {q12, q13},
[%1]!             \n"

"vld1.32     {q14, q15},
[%1]!             \n"

"subs       %2, %2, #16                    \n"

// q0, q1, q2,
q3 是vsub的目的地寄存器.

// 也是vmla的源寄存器。

"vsub.f32   q0, q0, q12                    \n"

"vmla.f32   q8, q0, q0                     \n"

"vsub.f32   q1, q1, q13                    \n"

"vmla.f32   q9, q1, q1                     \n"

"vsub.f32   q2, q2, q14                    \n"

"vmla.f32   q10, q2, q2                    \n"

"vsub.f32   q3, q3, q15                    \n"

"vmla.f32   q11, q3, q3                    \n"

"bgt        1b                             \n"

"vadd.f32   q8, q8, q9                     \n"

"vadd.f32   q10, q10,
q11                  \n"

"vadd.f32   q11, q8, q10                   \n"

"vpadd.f32  d2, d22, d23                   \n"

"vpadd.f32  d0, d2, d2                     \n"

"vmov.32    %3, d0[0]                      \n"

:
"+r"(src_a),

"+r"(src_b),

"+r"(count),

"=r"(sse)

:

: "memory",
"cc", "q0", "q1", "q2", "q3",
"q8", "q9", "q10",
"q11","q12", "q13","q14",
"q15");

return sse;

}

// NEON实现二

float SumSquareError_NEON2(const float* src_a, const
float* src_b, int count)

{

float sse;

asm volatile (

"veor    q8, q8, q8                        \n"

"veor    q9, q9, q9                        \n"

"veor    q10, q10, q10                     \n"

"veor    q11, q11, q11                     \n"

"1:                                         
\n"

"vld1.32     {q0, q1},
[%0]!               \n"

"vld1.32     {q2, q3},
[%0]!               \n"

"vld1.32     {q12, q13},
[%1]!             \n"

"vld1.32     {q14, q15},
[%1]!             \n"

"subs       %2, %2, #16                    \n"

"vsub.f32   q0, q0, q12                    \n"

"vsub.f32   q1, q1, q13                    \n"

"vsub.f32   q2, q2, q14                    \n"

"vsub.f32   q3, q3, q15                    \n"

"vmla.f32   q8, q0, q0                     \n"

"vmla.f32   q9, q1, q1                     \n"

"vmla.f32   q10, q2, q2                    \n"

"vmla.f32   q11, q3, q3                    \n"

"bgt        1b                             \n"

"vadd.f32   q8, q8, q9                     \n"

"vadd.f32   q10, q10, q11                  \n"

"vadd.f32   q11, q8, q10                   \n"

"vpadd.f32  d2, d22, d23                   \n"

"vpadd.f32  d0, d2, d2                     \n"

"vmov.32    %3, d0[0]                      \n"

:
"+r"(src_a),

"+r"(src_b),

"+r"(count),

"=r"(sse)

:

:
"memory", "cc", "q0", "q1",
"q2", "q3", "q8", "q9",
"q10", "q11", "q12", "q13","q14",
"q15");

return sse;

}

/***************************************************************/

在NEON实现一中,我们把目的寄存器立刻当作源寄存器;在NEON实现二中,我们重新排布了指令,并给予目的寄存器尽量多的延时。经过测试实现二比实现一快30%。由此可见,降低数据依赖性对于提高程序性能有重要意义。一个好消息是编译器能自动调整NEON
intrinsics以降低数据依赖性。这个利用NEON intrinsics的一个很大优势。

2. 减少跳转

NEON指令集没有跳转指令,当需要跳转时,我们需要借助ARM指令。在ARM处理器中,分支预测技术被广泛使用。但是一旦分支预测失败,惩罚还是比较高的。因此我们最好尽量减少跳转指令的使用。其实,在有些情况下,我们可以用逻辑运算来代替跳转,如下例所示:

ARM NEON指令集提供了下列指令来帮助用户实现上述逻辑实现:

/***************************************************************/

// C实现

if( flag )

{

dst[x *
4]     = a;

dst[x * 4 +
1] = a;

dst[x * 4 + 2] = a;

dst[x * 4 +
3] = a;

}

else

{

dst[x *
4]     = b;

dst[x * 4 +
1] = b;

dst[x * 4 +
2] = b;

dst[x * 4 +
3] = b;

}

// NEON实现

//dst[x * 4]     =
(a&Eflag) | (b&~Eflag);

//dst[x * 4 + 1] = (a&Eflag) | (b&~Eflag);

//dst[x * 4 + 2] = (a&Eflag) | (b&~Eflag);

//dst[x * 4 + 3] = (a&Eflag) | (b&~Eflag);

VBSL qFlag, qA, qB

/***************************************************************/

• VCEQ, VCGE, VCGT, VCLE, VCLT……

• VBIT, VBIF, VBSL……

减少跳转,不仅仅是在NEON中使用的技巧,是一个比较通用的问题。即使在C程序中,这个问题也是值得注意的。

3. 其它技巧

在ARM NEON编程时,一种功能有时有多种实现方式,但是更少的指令不总是意味着更好的性能,要依据测试结果和profiling数据,具体问题具体分析。下面列出来我遇到的一些特殊情况。

4. 浮点累加指令

通常情况下,我们会用VMLA/VMLS来代替VMUL + VADD/
VMUL + VSUB,这样使用较少的指令,完成更多的功能。但是与浮点VMUL相比,浮点VMLA/VMLS具有更长的指令延时,如果在指令延时中间不能插入其它计算的情况下,使用浮点VMUL + VADD/ VMUL + VSUB反而具有更好的性能。

一个真实例子就是Ne10库函数的浮点FIR函数。代码片段如下所示:

实现1:在两条VMLA指令之间,仅有VEXT指令。而根据指令延时表,VMLA需要9个周期。

实现2:对于qAcc0,依然存在指令延时。但是VADD/VMUL只需要5个周期。

ARM NEON指令集优化理论与实践的更多相关文章

  1. ARM NEON 编程系列2 - 基本指令集

    ARM NEON 编程系列2 - 基本指令集 前言 本系列博文用于介绍ARM CPU下NEON指令优化. 博文github地址:github 相关代码github地址:github NEON指令集 主 ...

  2. 【C#代码实战】群蚁算法理论与实践全攻略——旅行商等路径优化问题的新方法

    若干年前读研的时候,学院有一个教授,专门做群蚁算法的,很厉害,偶尔了解了一点点.感觉也是生物智能的一个体现,和遗传算法.神经网络有异曲同工之妙.只不过当时没有实际需求学习,所以没去研究.最近有一个这样 ...

  3. ARM NEON编程系列1-导论

    ARM NEON 编程系列1 - 导论 前言 本系列博文用于介绍ARM CPU下NEON指令优化. 博文github地址:github 相关代码github地址:github NEON历史 ARM处理 ...

  4. Java 理论和实践: 了解泛型

    转载自 : http://www.ibm.com/developerworks/cn/java/j-jtp01255.html 表面上看起来,无论语法还是应用的环境(比如容器类),泛型类型(或者泛型) ...

  5. Java 理论与实践: 并发集合类

    Java 理论与实践: 并发集合类 DougLea的 util.concurrent 包除了包含许多其他有用的并发构造块之外,还包含了一些主要集合类型 List 和 Map 的高性能的.线程安全的实现 ...

  6. 监督学习——决策树理论与实践(下):回归决策树(CART)

    介绍 决策树分为分类决策树和回归决策树: 上一篇介绍了分类决策树以及Python实现分类决策树: 监督学习——决策树理论与实践(上):分类决策树          决策树是一种依托决策而建立起来的一种 ...

  7. 高翔《视觉SLAM十四讲》从理论到实践

    目录 第1讲 前言:本书讲什么:如何使用本书: 第2讲 初始SLAM:引子-小萝卜的例子:经典视觉SLAM框架:SLAM问题的数学表述:实践-编程基础: 第3讲 三维空间刚体运动 旋转矩阵:实践-Ei ...

  8. SEO从理论到实践

    GITHUB:http://www.liu12fei08fei.top/blog/12seo.html 明白seo是什么 知道怎么做 SEO从理论到实践 什么是SEO? SEO和SEM的区别 SEO和 ...

  9. Java 理论与实践: 修复 Java 内存模型,第 2 部分(转载)

    在 JSR 133 中 JMM 会有什么改变? 活跃了将近三年的 JSR 133,近期发布了关于如何修复 Java 内存模型(Java Memory Model, JMM)的公开建议.在本系列文章的 ...

随机推荐

  1. hdu4496并查集的删边操作

    题意:       给你一个图,问你删除一些边后还有几个连通快.. 思路:       典型的并查集删边操作,并查集的删边就是先把不删除的边并查集一边(本题没有不删除的边),然后逆序吧所有要删除的边以 ...

  2. POJ1178枚举三个地方(所有点都去同一个点)

    题意:       有一个国王和很多骑士,他们都要到某一个点去集合,然后问所有人都到达某个终点的距离和最小是多少?过程中如果国王遇到了一个骑士的话,国王就可以和骑士一起按照骑士的走法走,这是两个人算一 ...

  3. JAVA WEB应用目录

    目录 Tomcat目录 MyEclipse目录 部署启动应用 Tomcat目录 首先,官方下载 tomcat 安装包,解压.得到下面的目录 bin 目录是tomcat启动和关闭目录.startup.b ...

  4. FFmpeg应用实践之命令查询

    0. 前言 FFmpeg 中常用的工具有三个,分别是多媒体编解码工具ffmpeg.多媒体内容分析工具ffprobe和多媒体播放器ffplay.本文介绍的指令都是与编解码工具 ffmpeg 相关的. 学 ...

  5. JAVA并发(1)-AQS(亿点细节)

    AQS(AbstractQueuedSynchronizer), 可以说的夸张点,并发包中的几乎所有类都是基于AQS的. 一起揭开AQS的面纱 1. 介绍 为依赖 FIFO阻塞队列 的阻塞锁和相关同步 ...

  6. CRM系统自动化业务流程的好处

    CRM系统的自动化可以简单化企业大部分工作,而且覆盖销售,市场,服务和财务部门,使团队有大量时间花在业务流程上. CRM系统自动化的建立是为了更好地使工作更为简单且便于管理,自动化释放出来员工的时间, ...

  7. MSSQL·查询存储过程中的关键字

    阅文时长 | 0.22分钟 字数统计 | 408字符 主要内容 | 1.引言&背景 2.声明与参考资料 『MSSQL·查询存储过程中的关键字』 编写人 | SCscHero 编写时间 | 20 ...

  8. 痞子衡嵌入式:串行NOR Flash的Continuous read模式下软复位后i.MXRT无法启动问题解决方案之RESET#

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT上使能NOR Flash的Continuous read模式在软复位后无法正常启动问题的解决经验. 前一篇文章 <在i ...

  9. Kibana常用语法

    GET brand201811_v2/_search 方法一:查询数据源,及相关url的文章 { "query": { "bool": { "must ...

  10. [转发]PotPlayer 无损截取视频片段

    PotPlayer 无损截取视频片段 2019-03-29 21:04:21 ForeverStrong 阅读数 2928  收藏 更多 分类专栏: 视频图像编辑   PotPlayer 无损截取视频 ...