JPEG 编解码在 Cell 上的优化

 

JPEG 编解码在 Cell 上的优化

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

简介

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

 

 

JPEG 编解码过程

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


1JPEG编码过程

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


2JPEG 解码过程

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


3planarToInterleaved 过程

具体的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 编解码过程

 

性能分析

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

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

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

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

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

 


 

SPU 上的优化

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

  1. SIMD
  2. 减小branch missdependency
  3. double-bufferingmulti-buffering

SIMD

一般来说,算法比较规则、数据之间依赖性较小的部分容易做SIMD,我们主要在以下几个方面做了SIMD优化:

1 IDCTSIMD

IDCT算法比较规则,适合做SIMD的优化,具体SIMD过程如图5所示。

步骤一:左上角的数据是输入数据,为8x8的矩阵,按照上四行和下四行分为两部分,分别放入两组向量中去。注意,这一步骤中,向量的元素在内存中并不连续,因此需要对原始数据进行转置,这个转置的操作可以在进行反量化的同时进行,这样就不需要额外再操作一次。

步骤二:完成向量的提取后,分别对上半部和下半部向量组进行1DCT变换(完成行变换),得到右上角的中间矩阵(vbv) 因为每个向量中存放四个元素,一次运算可以得到四行的结果。再对中间矩阵按照左半部和右半部得到的两个向量组cvrcv,分别为左半部和右半部的向量。这个过程,也就是由(vbv)得到(cvrcv),等效于进行了转置操作,可以利用如下程序片断,较高效地完成这一操作,不需要任何写回内存的操作。

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,0x
      
      
      
      
       
       0A
      
      ,0x0B,0x18,0x19,0x
      
      
       
       1A
      
      ,0x1B,0x
      
      
       
       0C
      
      ,0x0D,0x0E,0x
      
      
       
       0F
      
      ,0x
      
      
       
       1C
      
      ,0x1D,0x1E,0x
      
      
       
       1F
      
      
      
      
};
      
      
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);
      
      

 

对这两组向量(cvrcv)再进行1DCT变换(完成列变换),得到最后的变换结果。

步骤三:步骤二中得到的结果向量,每个向量中只包含四个字节的有效数据,直接存储到内存中去会比较低效,因此将一行中相邻的8个字节压缩到一个向量中去,再进行后续的操作会更高效。

步骤四:将向量依次写入内存。


5IDCT SIMD

2 FDCTSIMD

FDCT算法是IDCT的逆算法,SIMD的过程也基本是IDCT的逆过程。所不同之处是,对于IDCT而言,输入数据来自于Huffman解码后的数据,需要反量化处理后才能进行运算,运算完成后得到的就是颜色空间中的数据了,一般就是YCbCr分量的值;而对于FDCT来说,输入来自于YCbCr的原始数据,输出则需要进行量化运算。考虑到量化运算本身也具有一定的计算复杂度,IDCTFDCT在起始部分与结尾部分向量的处理要有所区别。

3 bitmapInputSIMD

bitmapInputJPEG的编码过程中实现了颜色转换(数据从BGR色系到YCbCr色系)和数据分离(将BGR排列的数据转化为YCbCr分开的数据,便于做后面的FDCT),其具体SIMD过程如图6所示:


6bitmapInput SIMD

4 planarToInterleavedSIMD

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


7planarToInterleaved SIMD

减小 branch miss dependency

在各个部分的CPU cycle结果中,当branch missdependencycycle超过20%比率的时候,就应该考虑减小branch missdependency,,我们主要在以下几个方面做了优化:

1 huffmanDecoder

解码过程中需要判别是否为0xFF的字节,然后忽略紧随的0x00。假定JPEG文件的数据区域段中0xFF均为实际的图像数据,可以进行如此优化:对于压缩过的bit流,每次提取64bits到一个vector中,64bit8bytes,因此用一个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

当性能测试的结果表明,channelcycle比率较高的时候,需要考虑double-buffering或者multi-buffering。这时候,程序的数据DMA传输时间大于数据处理时间,需要减小等待数据DMA的时间。double-bufferingmulti-buffering的一种形式,当使用两段buffer的时候,称为double-buffering。我们主要在如下几个方面做了multi-buffering的处理:

1 bitmapInputdouble-buffering

bitmapInputJPEG的编码过程中实现了bmp图片的数据DMA读取,其double-buffering过程如图 8 所示:


8bitmapInput double-buffering

2 bitmapOutputmulti-buffering

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


9bitmapOutput multi-buffering

 

结果分析

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


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


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

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

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


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


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

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

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

 

结束语

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

 

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值