xilinx zynq-7000中ARM NEON的使用

读《xapp1206-boost-sw-performance-zynq7soc-w-neon.pdf》,有感


2014年6月出的文章诶,发现的好晚,当年刚弄的时候是2013年8月份


比之前看的zynq-7000 swdev要专业一点,主要围绕了NEON来讲了


前面overview部分简要说了下文章主要内容,其中有一些东东还是有些价值的,后面一起说:


文章先讲了下为啥优化,

Software Optimization Basics部分:说了一堆可以优化的目的,文章主要讲速度,2种方法可以加速:①改变算法,避免cache颠簸;②理解cpu硬件实现再来优化。这里有几个知识点:

a. 读数据时cahce miss 了,要数十个cycles才能读出来,此时指令就stall了

b. DRAM 一些情况下能把延迟提高上百个cycles


之后全是再讲无法精准分析出软件跑的具体时间,这里要用到profiling工具,对于standalone来说,应该用Gprof(准备回来在SDK上试一下)


NEON Basics部分:先讲SIMD是啥,然后介绍NEON、VFP共享的32个寄存器,NEON和VFP的联系和区别,这里有几个知识点:

a. ARM普通CPU适合直接32-bit计算,不适合8-bit、16-bit计算(因为需要多余的控制防overflow之类的)

b. SIMD指令通常比相同功能的ARM指令需要的cycles数还要少

c. zynq里,NEON和VFP是集成一体的,统称为“FPU”,且共享全部32个64-bit寄存器(某文章说VFP只用了D0-D15简直是……)

d. NEON只能处理向量,只支持单精度浮点;VFP还可以处理非向量,支持双精度浮点,还能做像平方根、除法等这些NEON不能做的运算


之后又开始讲了NEON使用的4种方法:

1. 使用别人弄好的库(如Project Ne10、OpenMAX DL等),回来可以具体看看文档的Table 4

2. 自动向量化,介绍了好多编译选项,需要注意的是

-std=c99:之前一直没加这个,不知道有没有影响

-o3自动开了-ftree-vectorize,所以-o3时可以不加

-mvectorize-with-neon-quad:由于GCC 4.4只对双字自动向量化,加了这个就可以对四字了

-mcpu=cortex-a9:今天看了下verbose,SDK是自动加了-mfpu=neon和-mfloat-abi=softfp,但-mcpu=cortex-a9没加,不知道有没有影响

此外,-ftree-vectorizer-verbose=n(n=0~9)可以看向量化的信息

要求c语言部分的修改为:

a. 指定迭代次数

b. 避免迭代依赖和迭代里出现条件判断

c. 使用 restrict 修饰

d. 尽量使用最小的数据类型

这里需要注意以下几点:

a. 一定要用 restrict 修饰符(貌似这样才能识别)

b. 现在不需要unrolled loop了,unrolled loop反而不容易被识别了

c. 自动向量化方法下,浮点操作可能结果有偏差,但也是对的

标准的一个实例:

float dot_product(float * restrict pa, float * restrict pb, unsigned int len)
{
float sum=0.0;
unsigned int i;
for( i = 0; i < ( len & ~3); i++ ) // 注意这里是i++,没有unrolled loop
sum += pa[i] *pb[i];
return sum;
}

3. 内建函数 Intrinsics

使用原因:①自动向量化对于复杂算法就不行了 ②一些NEON指令没有等效的C表达式

优点:①不用分配具体的NEON寄存器 ②编译器帮你做了代码调度和指令重排

具体的就不说了,需要注意有一条指令:

vreinterpret,转换数据类型,但不生成具体代码,如:

uint8x8_t byteval;
uint32x2_t wordval;
byteval = vreinterpret_u8_u32(wordval);

4. 直接NEON汇编

优点不用说,缺点:①NEON汇编是processor-dependent的,一种Cortex-A处理器跑的好的另一种Cortex-A处理器就不一定了 ②难

需要考虑以下三个方面:

1. 存储器访问优化:主要推荐按tiles来跑算法,此外还要合理利用非交织/交织/解交织 读取和存储

2. 对齐:对齐肯定要比不对齐的快,语法[:],还要注意cache line 的对齐,如果数据跨了cache line,要多等数十个cycles呢

3. 指令调度:(窃以为这是最重要的)又包括以下3点

a. 结果使用调度(Result-use scheduling):就是注意上一次指令的结果不一定在下一个周期就出来,写汇编的时候要分配好

b. 内部锁(interlock):主要就是讲 vld之后的数据不一定可以马上就用(比如cache miss时,等数十个cycles),可以用“preload”来预读cache

c. 一直说读内存,写内存也会stall,比如write buffers满了


重头戏来了,如何优化NEON汇编

主要针对cache使用的优化上,提到了preload和防止cache颠簸的tile方法,下面详细描述:

1. 一条指令读/存尽量多的内存数据到寄存器(或寄存器到内存),一条指令传数据越多越好

2. 使用preload引擎

a. preload引擎专有的单元,不浪费ALU和读存单元

b. preload的同时可以执行PLD之后的代码(不会拖慢指令执行)

c. 3个指令:PLD、PLI和PLDW(preload data with intent to write)

d. 为达到hot cache,两种额外的提高cache hit方法:①在算法开始前定期pld lead data一下 ②多做几次pld (这两种方法都需要掐准lead time时机,preload太早了,读进来的数据会被挤掉,太晚了就没用了)

3. tile化,就是把一大片2维数组,划分为一小块一小块的tile,比如64x64的图像,划分为8x8的片(注意,由于zynq中L1的cacheline是32bytes,对于int型数据,可以1cacheline 为最小尺寸)


题外的知识点:

1. 压栈时,比如用 LDMIA R10!, { R0-R3, R12 },这里寄存器列表里的顺序是不重要的,ARM自动会把低序号寄存器映射到低地址位上

2. cold cache:指算法开始的时候没有任何数据在cache里,比如先执行了Xil_DCacheFlush(),再跑程序;

    hot cache:反之

3. lmbench开源项目可以测ddr的延迟时间,对于667MHz CPU、533MHz DDR3(不就是Zed么),延迟为60-80 CPU cycles

4. cache颠簸(cache thrashing):cache里不停的cache fill相同的line,刚fill的数据一会就被挤走之类的

你可能感兴趣的:(学习笔记)