IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Linux  >

JPEG 编解码在 Cell 上的优化

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 中级

魏琼, 软件工程师, IBM中国软件开发中心
龚志刚, 高级软件工程师, IBM中国软件开发中心
王佰玲, 高级软件工程师, IBM中国软件开发中心
潘家铭, 软件工程师, IBM中国软件开发中心

2007 年 6 月 28 日

JPEG编解码是比较常用的图像处理过程,本文主要介绍如何充分利用 Cell 的特性,优化 JPEG 的编解码过程,使其达到最佳性能。本文虽然主要是说明 JPEG 编解码的优化过程,但是其中使用到的优化方法,也是 Cell 上常用的优化手段,对于其他应用程序在 Cell 上的优化也很有借鉴作用。

简介

本文使用的 JPEG 编解码源代码属于 IBM 公司所有。读者如果希望按照本文提到的优化方法来进行 Cell 上 JPEG 编解码的优化,可以使用开源的 JPEG 编解码源代码(下载地址见 参考资料)来作为优化的基础。本文主要是介绍如何在一个 SPU 上优化 JPEG 编解码过程,不涉及将数据切分到不同 SPU 上的问题。





回页首


JPEG 编解码过程

本文提到的 JPEG 编码指的是从 bmp 图像文件格式到 jpeg 图像文件格式的转化过程,并且将其划分为如下几个主要部分来说明,如图 1 所示:


图1:JPEG编码过程
JPEG编码过程

本文提到的JPEG解码指的是从jpeg图像文件格式到bmp图像文件格式的转化过程,并且将其划分为如下几个主要部分来说明,如图 2 所示:


图2:JPEG 解码过程
JPEG 解码过程

planarToInterleaved这一步指的是将Y、Cb、Cr分开的几段buffer,经过颜色转化后,存储为BGR间隔排列的一段buffer,最后这段buffer会被写入bmp文件,如图3所示:


图3:planarToInterleaved 过程
planarToInterleaved 过程

具体的JPEG编解码原理见 参考资料





回页首


程序移植

  1. 使程序在PPU上正常运行起来。一般来说,适用于x86的源代码,如果在Linux上可以正常运行的话,移植到Cell上基本不需要太多的改动的。但是,因为x86平台是little-endian存储数据的,Cell平台是big-endian的,所以在程序中如果有字节位操作的话,需要做些修改。
  2. 考虑整个程序流程中,哪些部分适合在PPU上执行,哪些部分适合在SPU上执行。我们将文件的读取和写入在PPU上执行,PPU获取到文件buffer和大小后通知SPU,然后由SPU完成编解码过程并将结果buffer写回主存,最后由PPU来写入文件。
  3. 考虑程序是否能够放在只有256K内存的SPU上,如果程序代码量太大,要精简代码或者考虑使用overlay技术。我们初始的源代码具备很多功能,可以做图片的切割、伸缩、颜色分离等等,为了能够移植到SPU上运行,我们将代码精简为处理较为单一的功能,从而最小化代码大小。
  4. 考虑处理的单元块大小是否合适。我们使用的原始代码每次处理的单元块为一个MCU行,和图像的宽度有关。这样,当图片很宽的时候,程序大小会超出256K的限制。所以,我们修改了每次的处理单元块,横向是固定的象素点(而不是和图像的宽度有关),纵向是一个MCU的行数。
  5. 考虑采用何种通信机制。PPU通过signal机制通知SPU开始JPEG编解码;SPU通过中断mailbox机制通知PPU其编解码结束。

图4:在 Cell 上运行的 JPEG 编解码过程
在 Cell 上运行的 JPEG 编解码过程




回页首


性能分析

整个程序的主要部分移植到SPU上后,需要分析目前性能的瓶颈在哪儿,然后开始做具体的优化工作。

我们在程序的各个部分中添加prof_clean、prof_start和prof_stop,使用mambo来分别测出各个部分的CPU cycle数及整个编解码过程的总CPU cycle数(具体操作步骤见 参考资料),这样就可以知道各个部分在整个编解码过程中所占的cycle数比率。

我们刚开始性能分析的结果表明,图1和图2所示的4个部分所占用的cycle数比率加在一起约等于100%,这4个部分就成为我们主要的观察范围。其中,FDCT和IDCT占用最多的cycle数比率,超过了50%。找到瓶颈所在后,再对这个部分做具体的性能分析,由mambo的结果找出不合理的cycle是branch miss、dependency还是channel stall,然后针对性的进行优化。

在不断的性能优化中,需要不断地重新用mambo来做性能分析,因为可能开始的时候某个部分是瓶颈,然后优化完这个部分,另外一个部分会成为瓶颈。我们将FDCT和IDCT做完优化后,其所占的cycle比率降为30%左右,而huffman部分cycle比率变高,成为我们优化的焦点;到后来,bitmapOutput和bitmapInput部分也出现比率较高的channel stall,我们优化了其DMA部分。

总之,在Cell平台上的程序性能优化,需要以性能分析的结果作为优化的依据。





回页首


SPU 上的优化

我们用到的优化方式主要有如下几点:

  1. SIMD
  2. 减小branch miss和dependency
  3. 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
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
bitmapInput 的 SIMD

4. planarToInterleaved的SIMD:

planarToInterleaved在JPEG的解码过程中实现了颜色转换(数据从YCbCr色系到BGR色系)和数据组合(将Y、Cb、Cr分开的数据组合为BGR排列的数据,最后写到bmp格式的文件中),其具体SIMD过程如图7所示:


图7:planarToInterleaved 的 SIMD
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
bitmapInput 的 double-buffering

2. bitmapOutput的multi-buffering:

bitmapOutput在JPEG的解码过程中实现了bmp图片的数据DMA写出,其multi-buffering过程和double-buffering基本相似。DMA写出的时候,分配了更多的buffer作为数据的写出缓冲,其过程如图9所示:


图9:bitmapOutput 的 multi-buffering
bitmapOutput 的 multi-buffering




回页首


结果分析

经过上述几个方面在SPU上的优化,和最初移植到Cell上的代码相比,JPEG的编解码性能提高很大,JPEG编码在一个SPU上优化的情况如图10所示,JPEG解码在一个SPU上优化的情况如图11所示:


图10:JPEG 编码优化代码和原始代码性能相比
JPEG 编码优化代码和原始代码性能相比

图11:JPEG 解码优化代码和原始代码性能相比
JPEG 解码优化代码和原始代码性能相比

由此可见,移植到Cell上的代码需要做一些特定的优化(特别是SIMD)后,才能达到较好的性能。

JPEG编码在一个SPU上达到的性能及cycel分布(由mambo测得)如表1所示,JPEG解码在一个SPU上达到的性能及cycel分布如表2所示:


表1:JPEG编码在一个SPU上的性能
表1:JPEG编码在一个SPU上的性能

表2:JPEG解码在一个SPU上的性能
表2:JPEG解码在一个SPU上的性能

*:图片使用的是EEMBC bechmark中的测试图片,每秒帧数也是由EEMBC bechmark测得(参见参考资料)。

由两个表格结果可以看出,优化后的cycle分布是比较合理的,dual cycle在20%左右,branch miss在10%左右,dependency在20%左右,channel stall控制在3%之内。各个cycel的分布和程序本身的算法比较相关,通过上面的两个表格可以看出,JPEG解码过程中的dependency cycel较高,这与解码算法中数据之间依赖性很大有关。Intel在其官方的IPP性能测试白皮书(参见 参考资料)中提到了JPEG编解码的速度,可以看出,一个SPU的性能可以与一个intel的P4相当。一个Cell芯片有8个SPU,并行处理数据的话,可以达到P4速度的8倍。





回页首


结束语

通过对于JPEG编解码在Cell上优化,我们发现这个独特的CPU的确具有很好的性能。这种异构的CPU使得资源调配的空间变大,我们可以利用一个SPU来做JPEG编解码,同时用另外的SPU来做别的计算处理工作。独特的SPU架构使得程序员有很大的空间来做性能的优化,充分发挥SIMD的优势。随着Cell上相应编程工具、编程框架、性能检测工具、和编译器的完善和改进,Cell上的编程和优化工作会更加简单和高效。



参考资料



作者简介

魏琼,IBM 中国软件开发中心,CETI 部门软件工程师,从事 cell blade server 上的应用库向量化编程工作,喜爱 Linux,精通 Linux 上的 C 编程。


龚志刚,IBM 中国软件开发中心,CETI 高级软件工程师。主要从事 Cell 芯片上的程序开发与优化,以及 Cell 芯片技术在中国的推广工作。


王佰玲,IBM 中国软件开发中心,CETI 高级软件工程师。主要从事 Cell 芯片相关研发工作。


潘家铭,IBM 中国软件开发中心,CETI 软件工程师。主要从事 Cell 芯片上的软件开发工作。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款