搜索
bottom↓
回复: 22

速度取决于算法,真的写得很好!(转)

[复制链接]

出0入0汤圆

发表于 2012-11-18 11:25:39 | 显示全部楼层 |阅读模式
速度取决于算法

最近在做dephi上位机的图像处理,只是兴趣吧。用的是VFW ,不过还有蛮多不是蛮清楚。
查资料的过程中发现了一篇蛮好的文章,转过来供大家参考,也想望大家有什么好的优化算法能够贴出来供大家参考下。
还有如果有Delphi 图像处理方面的大牛,希望指导下小弟,目前卡在了YUY2转RGB24上。USB免驱的摄像头貌似出来的是YUY2的图像。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
转过来的如下:

同样的事情,方法不一样,效果也不一样。比如,汽车引擎,可以让你的速度超越马车,却无法超越音速;涡轮引擎,可以轻松超越音障,却无法飞出地球;如果有火箭发动机,就可以到达火星。
代码的运算速度取决于以下几个方面
1、 算法本身的复杂度,比如MPEG比JPEG复杂,JPEG比BMP图片的编码复杂。
2、 CPU自身的速度和设计架构
3、 CPU的总线带宽
4、 您自己代码的写法
本文主要介绍如何优化您自己的code,实现软件的加速。
先看看我的需求
我们一个图象模式识别的项目,需要将RGB格式的彩色图像先转换成黑白图像。
图像转换的公式如下:
Y = 0.299 * R + 0.587 * G + 0.114 * B;
图像尺寸640*480*24bit,RGB图像已经按照RGBRGB顺序排列的格式,放在内存里面了。
我已经悄悄的完成了第一个优化
以下是输入和输出的定义:
#define XSIZE 640
#define YSIZE 480
#define IMGSIZE XSIZE * YSIZE
typedef struct RGB
{
unsigned char R;
unsigned char G;
unsigned char B;
}RGB;
struct RGB in[IMGSIZE]; //需要计算的原始数据
unsigned char out[IMGSIZE]; //计算后的结果
第一个优化
优化原则:图像是一个2D数组,我用一个一维数组来存储。编译器处理一维数组的效率要高过二维数组。
先写一个代码:
Y = 0.299 * R + 0.587 * G + 0.114 * B;
void calc_lum()
{
int i;
for(i = 0; i < IMGSIZE; i++)
{
double r,g,b,y;
unsigned char yy;
r = in[i].r;
g = in[i].g;
b = in[i].b;
y = 0.299 * r + 0.587 * g + 0.114 * b;
yy = y;
out[i] = yy;
}
}
这大概是能想得出来的最简单的写法了,实在看不出有什么毛病,好了,编译一下跑一跑吧。
第一次试跑
这个代码分别用vc6.0和gcc编译,生成2个版本,分别在pc上和我的embedded system上面跑。
速度多少?
在PC上,由于存在硬件浮点处理器,CPU频率也够高,计算速度为20秒。
我的embedded system,没有以上2个优势,浮点操作被编译器分解成了整数运算,运算速度为120秒左右。
去掉浮点运算
上面这个代码还没有跑,我已经知道会很慢了,因为这其中有大量的浮点运算。只要能不用浮点运算,一定能快很多。
Y = 0.299 * R + 0.587 * G + 0.114 * B;
这个公式怎么能用定点的整数运算替代呢?
0.299 * R可以如何化简?
Y = 0.299 * R + 0.587 * G + 0.114 * B;
Y = D + E + F;
D = 0.299 * R;
E = 0.587 * G;
F = 0.114 * B;
我们就先简化算式D吧!
RGB的取值范围都是0~255,都是整数,只是这个系数比较麻烦,不过这个系数可以表示为:0.299 = 299 / 1000;
所以 D = ( R * 299) / 1000;
Y = (R * 299 + G * 587 + B * 114) / 1000;
这一下,能快多少呢?
Embedded system上的速度为45秒;
PC上的速度为2秒;
0.299 * R可以如何化简
Y = 0.299 * R + 0.587 * G + 0.114 * B;
Y = (R * 299 + G * 587 + B * 114) / 1000;
这个式子好像还有点复杂,可以再砍掉一个除法运算。
前面的算式D可以这样写:
0.299=299/1000=1224/4096
所以 D = (R * 1224) / 4096
Y=(R*1224)/4096+(G*2404)/4096+(B*467)/4096
再简化为:
Y=(R*1224+G*2404+B*467)/4096
这里的/4096除法,因为它是2的N次方,所以可以用移位操作替代,往右移位12bit就是把某个数除以4096了。
void calc_lum()
{
int i;
for(i = 0; i < IMGSIZE; i++)
{
int r,g,b,y;
r = 1224 * in[i].r;
g = 2404 * in[i].g;
b = 467 * in[i].b;
y = r + g + b;
y = y >> 12; //这里去掉了除法运算
out[i] = y;
}
}
这个代码编译后,又快了20%。
虽然快了不少,还是太慢了一些,20秒处理一幅图像,地球人都不能接受。
仔细端详一下这个式子!
Y = 0.299 * R + 0.587 * G + 0.114 * B;
Y=D+E+F;
D=0.299*R;
E=0.587*G;
F=0.114*B;
RGB的取值有文章可做,RGB的取值永远都大于等于0,小于等于255,我们能不能将D,E,F都预先计算好呢?然后用查表算法计算呢?
我们使用3个数组分别存放DEF的256种可能的取值,然后。。。
查表数组初始化
int D[256],F[256],E[256];
void table_init()
{
int i;
for(i=0;i<256;i++)
{
D[i]=i*1224;
D[i]=D[i]>>12;
E[i]=i*2404;
E[i]=E[i]>>12;
F[i]=i*467;
F[i]=F[i]>>12;
}
}
void calc_lum()
{
int i;
for(i = 0; i < IMGSIZE; i++)
{
int r,g,b,y;
r = D[in[i].r];//查表
g = E[in[i].g];
b = F[in[i].b];
y = r + g + b;
out[i] = y;
}
}
这一次的成绩把我吓出一身冷汗,执行时间居然从30秒一下提高到了2秒!在PC上测试这段代码,眼皮还没眨一下,代码就执行完了。一下提高15倍,爽不爽?
继续优化
很多embedded system的32bit CPU,都至少有2个ALU,能不能让2个ALU都跑起来?
void calc_lum()
{
int i;
for(i = 0; i < IMGSIZE; i += 2) //一次并行处理2个数据
{
int r,g,b,y,r1,g1,b1,y1;
r = D[in[i].r];//查表 //这里给第一个ALU执行
g = E[in[i].g];
b = F[in[i].b];
y = r + g + b;
out[i] = y;
r1 = D[in[i + 1].r];//查表 //这里给第二个ALU执行
g1 = E[in[i + 1].g];
b1 = F[in[i + 1].b];
y = r1 + g1 + b1;
out[i + 1] = y;
}
}
2个ALU处理的数据不能有数据依赖,也就是说:某个ALU的输入条件不能是别的ALU的输出,这样才可以并行。
这次成绩是1秒。
查看这个代码
int D[256],F[256],E[256]; //查表数组
void table_init()
{
int i;
for(i=0;i<256;i++)
{
D[i]=i*1224;
D[i]=D[i]>>12;
E[i]=i*2404;
E[i]=E[i]>>12;
F[i]=i*467;
F[i]=F[i]>>12;
}
}
到这里,似乎已经足够快了,但是我们反复实验,发现,还有办法再快!
可以将int D[256],F[256],E[256]; //查表数组
更改为
unsigned short D[256],F[256],E[256]; //查表数组
这是因为编译器处理int类型和处理unsigned short类型的效率不一样。
再改动
inline void calc_lum()
{
int i;
for(i = 0; i < IMGSIZE; i += 2) //一次并行处理2个数据
{
int r,g,b,y,r1,g1,b1,y1;
r = D[in[i].r];//查表 //这里给第一个ALU执行
g = E[in[i].g];
b = F[in[i].b];
y = r + g + b;
out[i] = y;
r1 = D[in[i + 1].r];//查表 //这里给第二个ALU执行
g1 = E[in[i + 1].g];
b1 = F[in[i + 1].b];
y = r1 + g1 + b1;
out[i + 1] = y;
}
}
将函数声明为inline,这样编译器就会将其嵌入到母函数中,可以减少CPU调用子函数所产生的开销。
这次速度:0.5秒。
其实,我们还可以飞出地球的!
如果加上以下措施,应该还可以更快:
1、 把查表的数据放置在CPU的高速数据CACHE里面;
2、 把函数calc_lum()用汇编语言来写
其实,CPU的潜力是很大的
1、 不要抱怨你的CPU,记住一句话:“只要功率足够,砖头都能飞!”
2、 同样的需求,写法不一样,速度可以从120秒变化为0.5秒,说明CPU的潜能是很大的!看你如何去挖掘。
3、 我想:要是Microsoft的工程师都像我这样优化代码,我大概就可以用489跑windows XP了!
以上就是对《让你的软件飞起来》的摘录,下面,我将按照这位牛人的介绍,对RGB到YCbCr的转换算法做以总结。
Y = 0.299R + 0.587G + 0.114B
U = -0.147R - 0.289G + 0.436B
V = 0.615R - 0.515G - 0.100B
#deinfe SIZE 256
#define XSIZE 640
#define YSIZE 480
#define IMGSIZE XSIZE * YSIZE
typedef struct RGB
{
unsigned char r;
unsigned char g;
unsigned char b;
}RGB;
struct RGB in[IMGSIZE]; //需要计算的原始数据
unsigned char out[IMGSIZE * 3]; //计算后的结果
unsigned short Y_R[SIZE],Y_G[SIZE],Y_B[SIZE],U_R[SIZE],U_G[SIZE],U_B[SIZE],V_R[SIZE],V_G[SIZE],V_B[SIZE]; //查表数组
void table_init()
{
int i;
for(i = 0; i < SIZE; i++)
{
Y_R[i] = (i * 1224) >> 12; //Y对应的查表数组
Y_G[i] = (i * 2404) >> 12;
Y_B[i] = (i * 467) >> 12;
U_R[i] = (i * 602) >> 12; //U对应的查表数组
U_G[i] = (i * 1183) >> 12;
U_B[i] = (i * 1785) >> 12;
V_R[i] = (i * 2519) >> 12; //V对应的查表数组
V_G[i] = (i * 2109) >> 12;
V_B[i] = (i * 409) >> 12;
}
}
inline void calc_lum()
{
int i;
for(i = 0; i < IMGSIZE; i += 2) //一次并行处理2个数据
{
out[i] = Y_R[in[i].r] + Y_G[in[i].g] + Y_B[in[i].b]; //Y
out[i + IMGSIZE] = U_B[in[i].b] - U_R[in[i].r] - U_G[in[i].g]; //U
out[i + 2 * IMGSIZE] = V_R[in[i].r] - V_G[in[i].g] - V_B[in[i].b]; //V
out[i + 1] = Y_R[in[i + 1].r] + Y_G[in[i + 1].g] + Y_B[in[i + 1].b]; //Y
out[i + 1 + IMGSIZE] = U_B[in[i + 1].b] - U_R[in[i + 1].r] - U_G[in[i + 1].g]; //U
out[i + 1 + 2 * IMGSIZE] = V_R[in[i + 1].r] - V_G[in[i + 1].g] - V_B[in[i + 1].b]; //V
}
}
根据牛人的观点,这种算法应该是非常快的了,以后可直接使用了。^_^

出0入0汤圆

 楼主| 发表于 2012-11-18 11:27:50 | 显示全部楼层
以前总是觉得自己用的430跑不动,看了之后很有想法,决定把之前的程序优化下.

出10入18汤圆

发表于 2012-11-18 11:33:09 | 显示全部楼层
使用两个ALU计算是什么做到的?不太懂啊

出0入0汤圆

发表于 2012-11-18 11:35:37 | 显示全部楼层
给力!!!数表值是怎么来的?

出0入0汤圆

发表于 2012-11-18 11:43:07 | 显示全部楼层
需要补充一句:合适就是最好。  速度和性能是有矛盾的。  上面有许多优化优化是牺牲性能追求速度。

出0入0汤圆

发表于 2012-11-18 11:44:01 | 显示全部楼层
“使用两个ALU计算是什么做到的?不太懂啊”
我也不懂,求解释。

猜测可能是按照刚才他上面说的要求写:1、两段代码都写上;2、2个ALU处理的数据不能有数据依赖;这样编译器做优化时会自己实现优化。

出0入0汤圆

发表于 2012-11-18 11:51:47 | 显示全部楼层
这个好像是

感悟设计
      电子设计的经验与哲理

的一节

出0入0汤圆

发表于 2012-11-18 12:26:27 | 显示全部楼层
用空间换取时间!!在实际中,是需要权衡这两个因素的。

出0入0汤圆

 楼主| 发表于 2012-11-18 12:32:11 | 显示全部楼层
not_at_all 发表于 2012-11-18 11:43
需要补充一句:合适就是最好。  速度和性能是有矛盾的。  上面有许多优化优化是牺牲性能追求速度。 ...

其实对于我们这些才写程序不久的菜鸟很有必要的,因为我们写程序时开始
只是注重实现,完全没有考虑付出的代价啦

出0入0汤圆

 楼主| 发表于 2012-11-18 12:32:43 | 显示全部楼层
y574924080 发表于 2012-11-18 11:51
这个好像是

感悟设计

这个没有看过
不过觉得确实写得蛮好的

出0入0汤圆

 楼主| 发表于 2012-11-18 12:38:37 | 显示全部楼层
ming076 发表于 2012-11-18 11:44
“使用两个ALU计算是什么做到的?不太懂啊”
我也不懂,求解释。

这个我也不太明白,之前用过FPGA,这个ALU是算术逻辑单元,当我们将程序像样写的话
可以改成硬件加速,相当于将之前的单条线路改为两条线路同时执行,那样就会消耗算术逻辑单元
估计这里比以后会自己优化吧。
坐等大牛讲解

出0入0汤圆

发表于 2012-11-18 13:07:35 | 显示全部楼层
  最后一个循环没看懂  

出0入0汤圆

发表于 2012-11-18 13:47:21 | 显示全部楼层
3、 我想:要是Microsoft的工程师都像我这样优化代码,我大概就可以用489跑windows XP了!

你不想想开发难度呢。一个功能都这么费劲的话,电脑就光停留在486时代了。软件开发要平衡开发效率和执行效率啊。优化只在关键地方优化,不优化能接受就不优化了。还有良好的编码习惯,有良好的规划,良好的结构,也是为日后可能需要的优化做准备的。
也正是硬件一直在拖后腿才促进硬件性能发展的。

出0入0汤圆

发表于 2012-11-18 13:50:41 来自手机 | 显示全部楼层
mark            

出0入0汤圆

发表于 2012-11-18 16:24:03 | 显示全部楼层
估计是玩bf5xx的,这个有双alu,基本入门级工程师,当然还算是动了脑筋的,不只是个民工级别。
可是,这种优化和整体算法级优化差的太远,只能称之为数值算法优化。

出0入0汤圆

发表于 2012-11-18 20:14:46 来自手机 | 显示全部楼层
这个好像我也转发过

出0入0汤圆

发表于 2012-11-18 20:51:53 | 显示全部楼层
这些内容来自一个PPT。

出0入0汤圆

发表于 2012-11-18 22:48:12 | 显示全部楼层
int D[256],F[256],E[256];
void table_init()
{
int i;
for(i=0;i<256;i++)
{
D[i]=i*1224;
D[i]=D[i]>>12;
E[i]=i*2404;
E[i]=E[i]>>12;
F[i]=i*467;
F[i]=F[i]>>12;
}
}

如果i为16位整型数,D[i],E[i],F[i]都溢出了吧?
需要很多RAM。

出0入0汤圆

发表于 2012-11-18 23:05:39 | 显示全部楼层
这个PDF看起来会更简洁点

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2012-11-18 23:11:41 | 显示全部楼层
写机械手坐标反解程序的时候就是这么用的,感觉这些加速办法动动脑子都能想出来= =

出0入0汤圆

发表于 2012-11-18 23:17:00 | 显示全部楼层
速度,算法

出110入12汤圆

发表于 2012-11-19 00:41:18 来自手机 | 显示全部楼层
就是大道至简那本书里的

出0入0汤圆

发表于 2012-11-19 00:44:35 来自手机 | 显示全部楼层
好精彩,谢谢了               
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-7 05:05

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表