足迹

能看不尽景,始是不凡人

 

饱和运算的C/C++优化方案

定义:

 

 所谓饱和运算(saturation),就是当运算结果大雨某个上限或是小于某个下限时,结果就等于上限或下限。

 

 

 

  饱和运算是图像处理中比较基本的运算,在图像的滤波、亮度增强、编解码等操作中都会遇到这个操作,所以研究如何用最优的方式运行该运算就很有意义了。

由于我们最常见到的图像每个通道的位深为8比特,事实上我基本上没见过其他深度的。所以我们的讨论集中在如何优化8位图像的饱和运算。

首先,我们知道作为一个基本运算,由于我们需要频繁地使用,所以对它会有更高的要求,因为基本运算性能的降低会成为整个系统性能的瓶颈。

那么,一个好的运算算法的评价标准是什么呢?

(1)       时间复杂度低

(2)       空间要求少

   在这里,由于是作为内核的基本运算,所以我们对程序的可读性的要求会有所降低,毕竟鱼和熊掌不可兼得,我们需要有所割舍。

 

So,我们看看有什么算法吧。首先,也是最容易想到的就是:

 方法一、刀耕火种法

   最直观的方法是根据定义来做。下面给出源码:

Code

 

我们分析一下这个程序,它很直观,但是未必高效,其原因是什么呢:

(1)       对一个基本运算采用函数方式,增加了调用函数的时间开销。从c库函数的实现方式来看,一般基本操作需要优先考虑用宏定义的方式来解决,如getchar()等都是例证。

(2)       采用了if-else if-else的分支结构,既增加了系统判断的时间开销,又由于需要比较而增加了系统的空间开销。

i_pix > 255 语句

汇编后的代码为:cmp  ecx,0FFh

cmp指令是一个三字指令。

说到这里,顺便提一下 i_pix < 0

其汇编后代码为:test eax,eax

test指令是一字指令,所以我们在做比较操作的时候如有可能尽量要与0比较。当然,这是题外话。

 

以上两点提出了改进方向,一是尽量考虑用宏,二是考虑用比较运算符替代分支结构。

 

下面的问题是:比较运算符只有两个比较条件,有没有可能把三个分支合并成两个分支呢?

经过分析,我们发现一个现象:大于255和小于0的像素值偶一个共同特征,那就是,他们的高八位都是非零的。而且由于大于255的像素最后输出为25511111111),小于0的输出为000000000),与它们的符号相反,我们可以通过算术移位操作,把像素值的每位都填为符号位,然后再按位取反就行了。即,当pix越界时,输出为 ~(pix>>16) 或是(-pix>>16)就行了。

 

关键问题解决了,下面的事就很简单了:

 #define saturation (pix) (((pix)&(~255))?(-(pix))>>16 : (UINT8)(pix))

 

  然后就进行测试:

 

测试环境:

   操作系统: windows xp professional sp2

   CPUintel T2080 1.73Ghz

   内存:1.73Ghz, 504MB

   编译环境: VC++6.0

 

我们随机生成了一个[-500500]范围内的150*1000的数组,在Release版本下的性能为:

 

  算法1 15 ms

  算法2 0 ms

 

因为我们的作用对象是图像,一般规模都比较大,所以每幅图节约15ms还是很可观的。在视频条件下,按照每秒25帧的帧率来算的话,留给一帧的时间仅为40ms,这样看来节省15ms,算是很好的事了。

后来又突然想到,给算法1的函数前加了inline,使之变为内联函数,跑出来的结果为:

  算法1 0 ms

  算法2 0 ms

所以说在饱和运算的情况下,其实是函数调用对系统开销的影响最大,分支语句由于编译器的优化,所以已经不太占用系统开销了。

 

得到一个结论,那就是:很多时候优化首先要从技术角度来考虑。但是,一个巧妙的新算法给我们带来的成就感更大。一个用于商业,一个用于兴趣,都有用。

参考资源:

1.百度百科

http://baike.baidu.com/view/1547769.html?fromTaglist

2. H.264 中很有用的一些概念

http://hi.baidu.com/beily815/blog/item/09003c8d0c62271ab31bba46.html

posted on 2009-08-26 21:49  姚伟峰  阅读(3355)  评论(3编辑  收藏  举报

导航