|
SPU 上的优化
我们用到的优化方式主要有如下几点:
- SIMD
- 减小branch miss和dependency
- double-buffering和multi-buffering
SIMD
一般来说,算法比较规则、数据之间依赖性较小的部分容易做SIMD,我们主要在以下几个方面做了SIMD优化:
1. IDCT的SIMD:
IDCT算法比较规则,适合做SIMD的优化,具体SIMD过程如图5所示。
步骤一:左上角的数据是输入数据,为8x8的矩阵,按照上四行和下四行分为两部分,分别放入两组向量中去。注意,这一步骤中,向量的元素在内存中并不连续,因此需要对原始数据进行转置,这个转置的操作可以在进行反量化的同时进行,这样就不需要额外再操作一次。
步骤二:完成向量的提取后,分别对上半部和下半部向量组进行1维DCT变换(完成行变换),得到右上角的中间矩阵(v,bv)。因为每个向量中存放四个元素,一次运算可以得到四行的结果。再对中间矩阵按照左半部和右半部得到的两个向量组cv和rcv,分别为左半部和右半部的向量。这个过程,也就是由(v,bv)得到(cv,rcv),等效于进行了转置操作,可以利用如下程序片断,较高效地完成这一操作,不需要任何写回内存的操作。
register vector unsigned char hi = (vector unsigned char){
0x00,0x01,0x02,0x03,0x10,0x11,0x12,0x13,0x04,0x05,0x06,0x07,0x14,0x15,0x16,0x17
};
register vector unsigned char lo = (vector unsigned char){
0x08,0x09,0x0A,0x0B,0x18,0x19,0x1A,0x1B,0x0C,0x0D,0x0E,0x0F,0x1C,0x1D,0x1E,0x1F
};
tempV = spu_shuffle(v0, v2, hi);
tempV_1 = spu_shuffle(v0, v2, lo);
tempV_2 = spu_shuffle(v1, v3, hi);
tempV_3 = spu_shuffle(v1, v3, lo);
cv0 = spu_shuffle(tempV, tempV_2, hi);
cv1 = spu_shuffle(tempV, tempV_2, lo);
cv2 = spu_shuffle(tempV_1, tempV_3, hi);
cv3 = spu_shuffle(tempV_1, tempV_3, lo);
|
对这两组向量(cv,rcv)再进行1维DCT变换(完成列变换),得到最后的变换结果。
步骤三:步骤二中得到的结果向量,每个向量中只包含四个字节的有效数据,直接存储到内存中去会比较低效,因此将一行中相邻的8个字节压缩到一个向量中去,再进行后续的操作会更高效。
步骤四:将向量依次写入内存。
图5:IDCT 的 SIMD
2. FDCT的SIMD:
FDCT算法是IDCT的逆算法,SIMD的过程也基本是IDCT的逆过程。所不同之处是,对于IDCT而言,输入数据来自于Huffman解码后的数据,需要反量化处理后才能进行运算,运算完成后得到的就是颜色空间中的数据了,一般就是YCbCr分量的值;而对于FDCT来说,输入来自于YCbCr的原始数据,输出则需要进行量化运算。考虑到量化运算本身也具有一定的计算复杂度,IDCT与FDCT在起始部分与结尾部分向量的处理要有所区别。
3. bitmapInput的SIMD:
bitmapInput在JPEG的编码过程中实现了颜色转换(数据从BGR色系到YCbCr色系)和数据分离(将BGR排列的数据转化为Y、Cb、Cr分开的数据,便于做后面的FDCT),其具体SIMD过程如图6所示:
图6:bitmapInput 的 SIMD
4. planarToInterleaved的SIMD:
planarToInterleaved在JPEG的解码过程中实现了颜色转换(数据从YCbCr色系到BGR色系)和数据组合(将Y、Cb、Cr分开的数据组合为BGR排列的数据,最后写到bmp格式的文件中),其具体SIMD过程如图7所示:
图7:planarToInterleaved 的 SIMD
减小 branch miss 和 dependency
在各个部分的CPU cycle结果中,当branch miss和dependency的cycle超过20%比率的时候,就应该考虑减小branch miss和dependency,,我们主要在以下几个方面做了优化:
1. huffmanDecoder
解码过程中需要判别是否为0xFF的字节,然后忽略紧随的0x00。假定JPEG文件的数据区域段中0xFF均为实际的图像数据,可以进行如此优化:对于压缩过的bit流,每次提取64个bits到一个vector中,64个bit是8个bytes,因此用一个256个单元的表就可以表示0xFF的分布状况,可以先计算出这样一个表,再利用向量运算直接查表即可完成对0xFF的处理。这样可以完全消除其过程中的判断语句,从而减小branch miss。
2. 其他减小branch miss的方法:
我们在头文件中定义了如下的宏来做branch hint:
#define unlikely(_c) __builtin_expect((_c), 0)
#define likely(_c) __builtin_expect((_c), 1)
然后,将所有的判断返回值错误的地方,如下代码:
if( returnCode != 0 )
{
return returnCode;
}
改为:
if( unlikely(returnCode != 0) )
{
return returnCode;
}
当某个条件大多数时候总是成立的时候,则使用likely来做branch hint。
double-buffering 和 multi-buffering
当性能测试的结果表明,channel的cycle比率较高的时候,需要考虑double-buffering或者multi-buffering。这时候,程序的数据DMA传输时间大于数据处理时间,需要减小等待数据DMA的时间。double-buffering是multi-buffering的一种形式,当使用两段buffer的时候,称为double-buffering。我们主要在如下几个方面做了multi-buffering的处理:
1. bitmapInput的double-buffering:
bitmapInput在JPEG的编码过程中实现了bmp图片的数据DMA读取,其double-buffering过程如图 8 所示:
图8:bitmapInput 的 double-buffering
2. bitmapOutput的multi-buffering:
bitmapOutput在JPEG的解码过程中实现了bmp图片的数据DMA写出,其multi-buffering过程和double-buffering基本相似。DMA写出的时候,分配了更多的buffer作为数据的写出缓冲,其过程如图9所示:
图9:bitmapOutput 的 multi-buffering
结果分析
经过上述几个方面在SPU上的优化,和最初移植到Cell上的代码相比,JPEG的编解码性能提高很大,JPEG编码在一个SPU上优化的情况如图10所示,JPEG解码在一个SPU上优化的情况如图11所示:
图10:JPEG 编码优化代码和原始代码性能相比
图11:JPEG 解码优化代码和原始代码性能相比
由此可见,移植到Cell上的代码需要做一些特定的优化(特别是SIMD)后,才能达到较好的性能。
JPEG编码在一个SPU上达到的性能及cycel分布(由mambo测得)如表1所示,JPEG解码在一个SPU上达到的性能及cycel分布如表2所示:
表1:JPEG编码在一个SPU上的性能
表2:JPEG解码在一个SPU上的性能
*:图片使用的是EEMBC bechmark中的测试图片,每秒帧数也是由EEMBC bechmark测得(参见参考资料)。
由两个表格结果可以看出,优化后的cycle分布是比较合理的,dual cycle在20%左右,branchmiss在10%左右,dependency在20%左右,channelstall控制在3%之内。各个cycel的分布和程序本身的算法比较相关,通过上面的两个表格可以看出,JPEG解码过程中的dependencycycel较高,这与解码算法中数据之间依赖性很大有关。Intel在其官方的IPP性能测试白皮书(参见 参考资料)中提到了JPEG编解码的速度,可以看出,一个SPU的性能可以与一个intel的P4相当。一个Cell芯片有8个SPU,并行处理数据的话,可以达到P4速度的8倍。 |
|