引言
本文基于ARM CortexA9处理器平台,对NEON的技术特性、指令功能、应用方法等技术内容进行研究,并利用JM解码器这个软件平台进行具体实现,提供NEON应用的实际效果,为打算使用NEON技术进行优化的研究人员和应用开发者提供技术参考和借鉴。
1 ARM CortexA9架构及NEON技术
1.1 ARM CortexA9架构
作为嵌入式处理器,ARM CortexA9处理器能提供优越的性能,并且功耗更低。CortexA9采用ARMv7架构,提供高效超标量流水线,并且可以利用CortexA9 NEON媒体处理引擎(MPE)或者浮点单元(FPU)来增强应用特殊性能,因而扩展了应用范围。图1显示了ARM体系结构的发展过程。
图1 ARM体系结构发展过程
1.2 NEON技术
CortexA9处理器集成的NEON技术,也称为先进SIMD扩展指令集。NEON包含16个128位寄存器,拥有100多条完整指令,并且拥有独立的寄存器系统和独立的硬件执行单元,支持8位、16位、32位、64位等数据类型的向量运算,最多可同时对16路8位数据进行并行计算,可用于2D/3D图形图像加速、音视频编解码、数字信号处理等应用。
NEON指令集与ARM或Thumb指令集可混合使用。这与使用外部加速器相比,能简化软件开发、调试和集成。ARM或Thumb指令集管理所有的程序流和同步,而NEON指令集可以实现结构化的存储访问、NEON和通用寄存器间的数据拷贝、数据类型转换、算术和逻辑并行处理等操作。
图2显示的是VADD.I16 Q0、Q1、Q2指令,实现了Q1和Q2向量寄存器中8路16位整数的并行加法,并将结果保存在Q0中。
图2 8路16位整数并行加法
2 NEON应用方法
NEON技术目前主要提供4种应用方法:自动向量化、NEON C内联函数优化、汇编语言优化、函数库优化。这4种方法特点如下。
(1) 自动向量化
采用ARM编译器提供的NEON向量化编译器,在编译时需要添加适当的命令行参数,例如“ftreevectorizemfpu=neon”。而且还可以对程序进行细微调整,使得编译器能够安全地进行向量化。例如,当两个指针指向的内存区域相互不重叠时,可以在指针前添加“__restrict”限定符来提示编译器内存使用安全。
另外,如果在for循环中每次循环的循环次数是4的倍数时,可以将循环次数最低两位屏蔽掉,以提示编译器,这些额外信息可以让编译器安全地使用NEON结构化加载和存储,进而使用NEON寄存器进行并行运算。采用这种方法进行优化的示例程序如下所示:
void add_ints(int*__restrict pa,int*__restrict pb,unsigned int n,int(x)){
unsigned int i;
for(i=0;i<(n&~3);i++)
pa[i]=pb[i]+x;
}
这种方法的好处是优化简单,并且其代码具备跨平台使用,也允许不同编码器编译。但往往优化的性能较差,这是由于编译器需确保向量优化前后的结果相同,在某些代码中,为了避免出现错误结果,编译器不会自动优化。
(2) NEON C内联函数优化
NEON C内联函数(intrinsics)是由ARM定义的一组全新的数据类型和内联函数,便于使用C语言直接访问NEON单元。在C/C++程序中,内联函数就同普通函数一样,但在编译时,这些内联函数会直接映射为NEON提供的向量指令。当前GCC编译器和ARM编译器都支持相同的NEON内联语法,只需在程序中添加“arm_neon.h”头文件,就可以使用NEON内联函数。NEON内联函数及数据型式举例如下:
#include<arm_neon.h>
uint32x4_t double_elements(uint32x4_t input){
return(vaddq_u32(input,input));
}
在上述程序中,uint32x4_t数据类型表示使用128位Q寄存器,且数据元素的数据类型为无符号32位整型数,而vaddq_u32函数实现对两个Q寄存器中的各个无符号32位整数元素进行相加。当前支持的所有NEON内联函数和数据类型,可以从GCC官网上查看。
使用内联函数进行优化,开发人员无需关注寄存器分配和互锁等问题,这些都交由编译器处理,而且编写程序比较容易,优化后的性能相对较高。但目前内联函数所提供的功能和灵活性仍远远比不上汇编指令,并且经过编译器编译后,会反复加载/存取寄存器数据,导致系统时钟的浪费。
(3) 汇编语言优化
采用汇编语言进行NEON的最底层优化,可以使优化性能最大化,但汇编语言比较灵活,手写汇编程序对开发人员来说具有较大挑战,如果使用不恰当,反而会影响优化性能。在C/C++程序中编写汇编代码主要有两种形式:汇编函数或内联汇编。
汇编函数中,需要声明代码段、操作堆栈等,过于复杂。而编写内联汇编,在C代码中需要以“asm”关键字标识,并在asm()编写汇编语句。这种方法只需要在待优化部分局部采用汇编语言实现,相对简单。
(4) 函数库优化
考虑到OpenMax DL和NE10目前提供的API功能很有限,例如OpenMax DL只支持H.264部分功能,并且其数据类型与JM解码器所采用的数据类型不太一致,需要添加和改写部分功能函数,容易破坏平台间的可移植性。
另外,考虑到目前内联函数所提供的功能也很有限,为了尽量挖掘NEON优化潜力,对JM解码器的优化策略是:对其中计算复杂度较高,并且算法可以改进得以并行化的模块,采用内联汇编方式进行优化,而对于其余模块或函数,则采用自动向量化的方法来利用编译器进行优化。
3 JM解码器NEON优化
JM解码器是H.264标准的官方参考实现,其代码按照H.264解码流程实现。H.264解码器中主要模块有熵解码、帧间运动补偿、反变换、帧内预测解码等。利用ARM CortexA9中的NEON引擎对解码器进行并行化,其前提是模块算法中对于不同像素点的运算过程可以并行进行,并且主要为算术运算。在H.264解码器中,由于熵解码模块的串行程度较高、条件分支过多,不适合采用NEON进行优化。而对于其他主要模块,其计算复杂度高,也存在适合NEON优化的部分,因此对这部分进行了NEON优化。优化过程的整体软硬件框架如图3所示。
图3 整体软硬件框架
由于优化的模块较多,这里仅以反整数余弦变换部分为例,来说明NEON优化的思路及具体实现。
H.264编码标准中对宏块采用了整数余弦变换(DCT),用于将时空域信号转化为频域信号,再进行量化,达到进一步压缩的目的。相应地,在H.264解码端,需要进行反整数余弦变换(IDCT),4×4宏块矩阵的IDCT的运算公式如下:
式中,Ci表示反变换运算的变换矩阵,而W为待变换的宏块系数。在JM解码器代码中,将上述二维矩阵运算分解为两次一维快速蝶形运算:第一次蝶形运算为实现CTi与W的矩阵相乘;第二次蝶形运算为实现CTi与W的结果与Ci相乘。IDCT中矩阵相乘的一维快速蝶形运算过程如图4所示。
图4 快速蝶形运算过程
图4中Xr(0)~Xr(3)表示4×4矩阵的同一列系数,按照阿拉伯数字排列分别从第一行至第4行。采用一维快速蝶形运算来实现IDCT算法,从C代码层面上已经有效降低了计算复杂度。但每次运算都只针对矩阵中一列系数,对于4×4矩阵来说,需要对4列系数进行4次相同运算,才能完成整个一维快速蝶形运算。而采用NEON进行并行优化的话,可以将4次运算合并为1次,这样可以大大提高运算速度。
在JM实现中,待变换矩阵中的系数采用16位无符号整型数表示,因而可以利用64位D寄存器保存一行4个系数,只需4个D寄存器就可保存所有矩阵系数。基于IDCT的快速蝶形运算思路,采用NEON对IDCT进行优化的整体思路如下:
① 加载4×4系数数据至4个D寄存器中,对CTiW相乘的快速蝶形运算采用NEON提供的向量加法、减法和移位指令,实现并行运算,结果仍保存在D寄存器中。并行运算过程如图5所示。L0~L3、S0~S3、X0~X3分别表示待变换矩阵W的每行元素、中间数据、快速运算最终结果。
图5 一维快速蝶形算法的NEON优化实现
② 对步骤①中4个保存结果CTiW的D寄存器进行转置操作。
③ 对步骤②中CTiW结果与最后一个变换矩阵相乘的快速蝶形运算采用NEON实现,与步骤①一致。
④ 转置并保存D寄存器中数据至存储区域中。
这里需要注意的是,在IDCT变换过程中,第一次一维快速蝶形运算中,待变换系数矩阵W逐列进行运算,而同一列系数存储至4个不同的D寄存器中,刚好对4个NEON寄存器进行运算操作。
而第二次一维快速蝶形运算,CTiW结果矩阵逐行进行运算,同一行元素存储在相同的D寄存器中,由于NEON运算主要针对寄存器之间进行,因此需要将结果矩阵进行转置。参照公式,由于矩阵转置公式XC=(XC)TT和(XC)T=CTXT,即XC=CTXT,CT也就是第一个变换矩阵。因此,在第二次快速蝶形运算前,对结果矩阵进行转置,即可按照原有的快速蝶形运算过程。在运算完成后,再进行转置,就能得到最终的反变换结果。具体转置过程及所使用NEON汇编指令如图6所示。
图6 4×4矩阵转置操作
IDCT快速蝶形运算的NEON优化实现,相应汇编实现如下所示:
"vadd.s32 q4,q0,q2\\n\\t"
"vsub.s32 q5,q0,q2\\n\\t"
"vsr.s32 q6,q1,#1\\n\\t"
"vsub.s32 q6,q6,q3\\n\\t"
"vshr.s32 q7,q3,#1\\n\\t"
"vadd.s32 q7,q1,q7\\n\\t"
"vadd.s32 q0,q4,q7\\n\\t"
"vadd.s32 q1,q5,q6\\n\\t"
"vsud.s32 q2,q5,q6\\n\\t"
"vsud.s32 q3,q4,q7\\n\\t"
4 JM解码器NEON优化效果测试
本文采用PandaBoard开发板作为测试平台,该开发板配置了TI公司的OMAP4430处理器(ARM CortexA9 MPCore),主频为1 GHz,RAM空间为2 GB。
软件平台采用基于Linux3.4.0的Ubuntu系统,并且采用ARM公司推出的Development Studio 5 (DS5)软件平台完成NEON优化代码的实现、调试以及测试工作。为了测试结果相对全面和准确,本文采用了多种测试序列格式进行解码测试,包括CIF(352×288)格式、VGA(640×480)格式和720P(1280×720)格式。