NEON介绍

转自:ARM NEON技术之NEON基础介绍 - 知乎

一、背景简介

ARM CPU 最初只有普通的寄存器,可以进行基本数据类型的基础运算。从 ARMv5 架构开始引入 VFP(vector-floating-point) 指令扩展,可以通过使用短向量指令来加速浮点计算。从 ARMv7 架构开始引入 NEON 技术,NEON 技术同样是依靠向量指令来加速计算。鉴于 NEON 技术提供的向量技术加速效果体验更优秀,从 ARMv7 架构开始使用 VFP 向量指令加速的模式被弃用,因此 VFP 单元有时也称之为 FPU(Floating Point Unit)单元。

NEON 技术意在通过加速音频和视频编解码、用户界面、2D/3D图形和游戏来改善多媒体用户体验。NEON 还可以加速信号处理算法,以加快音频和视频处理、声音和面部识别、计算机视觉和深度学习等应用。

二、基本架构

ARM NEON 技术本质上是一种高级的单指令多数据(SIMD)架构扩展,这种扩展仅在一些 ARMv7-A 和 ARMv7-R 架构以及 ARMv8 架构上支持。

熟悉 ARMv7-A 架构的应该知道 ARMv7 架构的内核是一个32位的系统,使用32位的寄存器。但是 NEON 单元使用的是64位或者128位的寄存器。这里的原因就是 NEON 单元使用独立的寄存器文件。不过,NEON 单元还是完全集成到处理器中的,可以和处理器共享整型操作单元、循环控制和缓存资源,相比于使用硬件加速器,大大降低了面积和功耗成本。而且由于 NEON 单元和应用程序使用相同的地址空间,可以使用更简单的编程模型。

ARM NEON 技术的核心是 NEON 单元,主要由四个模块组成,分别是 NEON 寄存器文件、整型执行流水线、单精度浮点执行流水线和数据加载和重排流水线。

NEON 单元

三、NEON 寄存器

NEON 寄存器主要是用来存放包含相同数据类型元素的向量。在 ARMv7 架构中, 一共有16个128位寄存器,这个128位寄存器也称之为 Q 寄存器,一个128位寄存器又可以分为两个64位寄存器,即一共有32个64位寄存器,64位寄存器又称之为 D 寄存器。在ARMv8 架构中寄存器的数量相比 ARMv7 架构数量翻倍。Q 寄存器和 D 寄存器对应表如下所示:

关系对应表

NEON 向量的元素数量取决于寄存器的类型和元素的数据类型。NEON 寄存器支持常见的数据类,包括整型和浮点类型等,具体如下所示:

NEON 寄存器元素类型

根据向量元素的数据类型以及寄存器的类型,向量的类型如下所示:

NEON 寄存器向量类型

从上表中可以看出,对于32位数据,比如 int 和 float 类型数据,一个 Q 寄存器包含四个元素,D 寄存器则包含两个元素。对于16位数据,比如 float16 和 short 类型,一个 Q 寄存器则包含八个元素, D 寄存器则包含四个元素。对于8位数据, 比如 char 和 poly 类型数据,一个 Q 寄存器包含十六个元素,D 寄存器则包含八个元素。

四、NEON 调用

ARM 平台提供了四种使用 NEON 技术的方式,分别为 NEON 内嵌函数、NEON 开源库、编译器自动向量化和 NEON 汇编。

调用方式

NEON 内嵌函数调用类似于普通函数调用,通过调用函数接口告知编译器需要优化的代码,编译器在编译阶段直接使用 NEON指令替换这些内嵌函数而不是执行类似子函数调用的操作。NEON 内嵌函数提供了一种低级的 NEON 指令访问方式,而编译器负责将 NEON 指令替换成汇编语言的复杂任务,主要包括寄存器分配和代码调度以及指令集重排,来达到获取最高性能的目标。NEON 内嵌函数的缺点在于汇编语言和开发代码不一致。

鉴于 NEON 指令的强大优化效果,市场上出现了很多支持 NEON 优化的开源库,比如 Ne10、OpenMAX、ffmpeg、Eigen3和Math-neon等。

矢量化编译器可以将 C 或 C++ 源代码进行矢量化,以实现对 NEON 硬件的有效使用。这意味着可以编写可移植的 C 代码,同时还可以获得 NEON 指令带来的性能水平。

当使用的是 ARM 编译器工具链的时候,可通过下图几种方式实现自动向量化:

自动向量化方式

当使用的是 GCC 编译器工具链的时候,可通过下图几种方式实现自动向量化:

使用 GCC 时需要指定 CPU,否则会使用编译环境默认的内核,导致代码无优化或者异常。如果开启 -O3 优化选项,则默认开启 -ftree-vectorize 选项。

如果想获取非常高的性能提升,手写 NEON 汇编优化代码是最有效的方法,GNU 汇编器 (gas) 和 ARM 编译器工具链汇编器 (armasm) 都支持 NEON 指令的汇编。ARM 嵌入式应用程序二进制接口 (EABI) 规定了哪些寄存器用于传递参数、返回结果或必须保留。

五、NEON 指令集

鉴于 NEON 指令集是处理向量类型的数据,所以这里先给出 NEON 指令集支持的向量类型,具体如下表所示:

NEON 向量类型

从上表可以看成,NEON 指令集的向量是由基本上就是常见的数据类型组成,根据 D 寄存器和 Q 寄存器以及数据类型形成多种向量类型。这些向量类型将作为 NEON 指令集的输入输出参数参与计算。

NEON 指令集提供了多种多样的功能,基本上可以满足绝大部分代码的使用需求,具体支持的功能如下图所示:

NEON 指令表

从上表中可以看出来 NEON 指令集支持的功能有加减乘法、特殊相邻元素加法、饱和乘法、乘累加、移位、逻辑运算、极值获取、BIT统计等。除此之外,还支持比较大小、取倒数、向量分割拼接重排、查表等功能。

此外,按照 NEON 指令的输出输入向量类型,还可以将 NEON 指令分为以下几种:

NEON 指令类型

常指令,结果向量和操作数向量的长度、元素类型一致。下图展示了以下指令的过程:

                                  VADD.I16 Q0, Q1, Q2

长指令通常对双字向量进行操作,并产生一个四字向量。结果元素的宽度是操作元素的两倍。长指令是用指令后面的 L 来指定的。下图显示了一个长指令的例子,输入操作数在操作前被提升为四字。

窄指令对四字向量操作数进行操作,并产生一个双字向量。结果元素是操作数元素宽度的一半。窄指令是用指令后面的 N 来指定的。如下图所示。

宽指令对一个双字向量操作数和一个四字向量操作数进行操作,产生一个四字向量结果。结果元素和第一操作数的宽度是第二操作数元素的两倍。宽指令有一个 W 附加在指令上。下图显示了这一点,输入的双字操作数在操作前被提升为四字。

六、优化示例

在图像处理时,彩色图转灰度图是很常见的一种图像格式转换。彩色图转灰度图的公式如下所示:

根据上述公式,实现彩色图转灰度图的 C 语言代码和 NEON 优化代码。代码如下所示:

static void BGR2GRAY(const unsigned char *bgr, int width, int height, unsigned char *gray) {
    int coef_q8[3] = { 29, 150, 77 };
    unsigned char *temp_s = (unsigned char*)bgr;
    unsigned char *temp_d = gray;
#ifdef USE_NEON
    uint8x8_t vwr = vdup_n_u8(coef_q8[0]);
    uint8x8_t vwg = vdup_n_u8(coef_q8[1]);
    uint8x8_t vwb = vdup_n_u8(coef_q8[2]);
#endif
    for (int row = 0; row < height; row++) {
        int col = 0;
#ifdef USE_NEON
        for ( ; col < width - 8; col += 8) {
            uint8x8x3_t vsrc = vld3_u8(temp_s);
            uint16x8_t vsum = vmull_u8(vsrc.val[0], vwr);
            vsum = vmlal_u8(vsum, vsrc.val[1], vwg);
            vsum = vmlal_u8(vsum, vsrc.val[2], vwb);
            vst1_u8(temp_d, vshrn_n_u16(vsum, 8));
            temp_d += 8;
            temp_s += 24;
        }
#endif
        for ( ; col < width; col++) {
            temp_d[0] = (temp_s[0] * coef_q8[0] + temp_s[1] * coef_q8[1] + temp_s[2] * coef_q8[2]) >> 8;
            temp_d += 1;
            temp_s += 3;
        }
    }
    return;
}

七、结语

NEON 技术所能探讨的内容远不止于此,这里仅仅介绍了 NEON 技术最基础的知识,更多是概括性介绍,后续将对每个点进行深度分析探讨。

八、附录

以下是部分官方文档地址链接

  • 4
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NEON是ARM架构中的一种SIMD(单指令多数据)技术,它可以在同一时钟周期内对多个数据进行并行处理,从而提高数据处理的效率。在NEON中,可以使用NEON寄存器来加速拷贝操作。 对于NEON拷贝,有两个引用内容提供了相关的函数实现。其中,引用给出了一个用NEON寄存器进行加速拷贝的函数memcpy_neon_64,它一次可以拷贝64字节,并适用于64字节的对齐拷贝。函数实现的伪代码如下: ```assembly void* memcpy_neon_64(void* dest, const void* src, size_t size) { mov r3, r0 // 保存返回值 0: PLD(pld [r1, #256]) // 预取数据 subs r2, r2, #64 // 计算剩余拷贝大小 vldmia.64 r1!, {d0-d7} // 从源地址加载数据到寄存器 vstmia.64 r0!, {d0-d7} // 将寄存器中的数据存储到目标地址 bgt 0b // 如果还有剩余数据,则继续循环 mov r0, r3 // 将返回值保存到r0寄存器中 mov pc, lr // 退出函数 } ``` 另外,引用提供了另一种实现方式,函数memcpy_1一次只拷贝一个字节,可用于对齐拷贝和非对齐拷贝。函数实现的伪代码如下: ```c void *memcpy_1(void *dest, const void *src, size_t count) { char *tmp = dest; const char *s = src; while (count--) *tmp++ = *s++; return dest; } ``` 如果你想要了解更多关于NEON指令的使用方法,可以参考GCC官方文档中的ARM NEON Intrinsics页面[3]。 综上所述,NEON memcpy是利用NEON寄存器来加速拷贝操作的一种技术,可以一次拷贝多个字节,提高数据处理效率。可以根据需求选择适合的NEON拷贝函数进行使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值