c语言循环优化

C语言常规优化策略


3 循环优化


提高程序效率的核心是对影响代码执行速度的关键程序段进行优化。在任何程序中,最影响代码速度的

往往是循环语句,特别是多层嵌套的循环语句。因此,掌握循环优化的各种实用技术是提高程序效率的

利器,也是一个高水平程序必须具备的基本功。
本节有关各种循环优化技术的讨论基本上以下面的一个程序段为对象,程序的涵义为:对于两个给定的

数组a、b,计算a[8]b[8]+a[12]b[12]+...+a[84]b[84]的值。原始的代码为:
// 版本0
prod=0;
i=1;
while (i<=20)
{
 x=*(a+4*i+4);
 y=*(b+4*i+4);
 z=x*y;
 prod+=z;
 i++;
}
常用的循环优化技术包括代码外提、删除冗余运算、强度削弱、变换循环控制条件、合并已知量以及删

除无用赋值等。下面我们围绕这一例子来介绍这些内容。

3.1 代码外提


代码外提是指将循环体中与循环变量无关的运算提出,并将其放到循环之外,以避免每次循环过程中的

重复操作。
在原始代码中,计算x、y的值时,重复使用到a+4, b+4操作,由于它们与循环变量i无关,可以放到循环

之外一次执行完成:
// 版本1
prod=0;
i=1;
a1=a+4;
b1=b+4;
while (i<=20)
{
 x=*(a1+4*i);
 y=*(b1+4*i);
 z=x*y;
 prod+=z;
 i++;
}

3.2 删除冗余运算


在版本1中,循环体内执行了两次4*i的操作,通过引入中间变量保留这一结果,可以删除冗余计算。
// 版本2
prod=0;
i=1;
a1=a+4;
b1=b+4;
while (i<=20)
{
 j=4*i;
 x=*(a1+j);
 y=*(b1+j);
 z=x*y;
 prod+=z;
 i++;
}


3.3 强度削弱与合并已知量

版本2还有进一步优化的必要,其中4*i这一乘法操作可根据问题的特点改换成加法操作。因为在两次相

邻的循环执行过程中,i的值相差4,为一个常数,因此可将j=4*i改换成j+=4,j的初值在循环外设置成

j=4*(i-1)。这一步称为强度削弱。在本例子中,由于i的初值为1,数j的初值为0,这一步称为合并已知

量。
// 版本3
prod=0;
i=1;
j=0;
a1=a+4;
b1=b+4;
while (i<=20)
{
 j+=4;
 x=*(a1+j);
 y=*(b1+j);
 z=x*y;
 prod+=z;
 i++;
}

3.4 变换循环控制条件

从版本3可以看到,循环变量i在循环体中除自身引用外,已不起任何作用,因此,可以将i从循环中删去

,其控制作用交给变量j来完成,由于在每次进入循环时,i、j之间总是保持关系j=4(i-1),从而可将循

环条件所改成j<=76。
// 版本4
prod=0;
j=0;
a1=a+4;
b1=b+4;
while (j<=76)
{
 j+=4;
 x=*(a1+j);
 y=*(b1+j);
 z=x*y;
 prod+=z;
}
在这一最后的版本中,除了变换循环控制条件外,还将有关i的无用赋值从循环内外全部驱除出去了。下

表列出了原始版本和最终版本在循环体内一些基本操作的差异:
  加法次数 乘法次数 赋值次数
原始版本 6  3  3
最终版本 4  1  3
其中,”+=“及”++”均算作一次加法。由此可见最终版本的效率约为原始版本的2倍。

3.5 循环优化的其它措施

循环优化是一个非常古老的话题,从第一个计算机程序诞生至今,一代一代的程序员已经积类起很多循

环优化的知识。例如,有一种称之为循环展开的方法在50,60年代非常流行,至今虽然一般程序员已不常

采用,但在一些特别追求效率的领域,如游戏程序编制,计算机动画等,还能找到这一方法的一丝踪迹

。循环展开的思想非常简单,如我们要计算一个数组前20项之和,通常的程序为:
s=0;
for (i=0; i<20;i++)
 s+=a[i];
采用最极端的循环展开法,上述程序可以写成:
s=a[0]+a[1]+...+a[19]
当然在程序中是不允许出现省略号的,这里我们只是一种示意性说明。很显然,根端的循环展开法在大

部分情况下是不可取的,例如,要计算数组前100项的和,我们决不会愚蠢到在程序中写出100数相加的

复杂表达式,即使你有时间和耐心去完成这项乏味的工作,大部分的编译程序都会对此提出警告,或者

在执行优化的过程中建议对表达式进行简化。一种变通的方式为: 仅在循环中展开几步,但仍保留循环

结构:
s=0;
for (i=0; i<20;)
{
 s+=a[i++];
 s+=a[i++];
 s+=a[i++];
 s+=a[i++];
}
据说,将循环展开2~7步可以提高程序效率达10%~20%。即便如此,除非无路可走,我是决不会采纳这

种措施的。
在循环优化的诸多措施中,最有效的措施要数强度削弱了,只有强度削弱才是改善循环效率的关键,作

为一个例子,大家可以对前面给出的中点线算法进行优化,将循环内的乘法全改为加法来完成。对于本

节给出的例子和中点线算法,循环中的乘法都是与循环变量线性相关的,对于非线性相关的情况,也能

够经过多次强度削弱,从而将乘法改变成加法。下面的例子是要计算前n个整数的平方和,我们来看看循

环变量的平方是怎样由加法来完成的。
S=0;
for (i=1; i<=n; i++)
 S+=i*i;
首先,两次连续的循环中i相差1,从而(i+)*(i+1)与i*i相差2*i+1,因此,上面的程序可以改进为:
S=0;
j=0;
for (i=1; i<=n; i++)
{
 j=2*i+1;
 S+=j;
}
线性乘法又可以进一步改进为加法:
S=0;
j=0;
k=-1;
for (i=1; i<=n; i++)
{
 k+=2;
 j+=k;
 S+=j;
}
当然还可以对上面的循环变量进行交换以进一步提高效率。
有关循环优化的最后一个课题是关于多重循环的组织问题。多重循环是需要加以有效地组织的,这可能

与我们通常的理解相反,比知说,我们要旋转一幅图象,一个典型的二重循环可以完成这一任务:
// ImageSre,lmageDst分别为原图象与目标图象
// cosV,sinV分别旋转角的余、正弦值
for (x=0; x<W; x++)  // W,H分别为原图象宽度和高度
 for (y=0; y<H; y++)
 {
  GetPixel(ImageSrc, x, y, color); // 取原图象一象素
  x’=x*cosV-y*sinV+x0;
  y’=x*sinV+y*cosV+y0;
  PutPixel(ImageDst, x’, y’, color);  // 置图象目标一象素
 }
这一程序可进行多方面的优化,但无论怎样改造,上面的程序总存在一个先天的不足:数据的处理次序

与存贮次序不一致,一幅图象,或者直观地说一个二维数组,在内存中总是逐行存放的,而在上面的处

理中却是逐列进行的,这会有什么问题呢?首先,两者之间的不一致会导致程序在许多方面优化不下去,

例如,我们可设计一个指针指向原图象的某一象素,要取下一象素时,只需将指针加1即可,而且,从第

一个象素至最后一个象素都能按这一方式统一处理,而如果逐列处理,当前处理的象素与下一象素位置

上相差太远,有时还需要跳来跳去,象素指针的这种统一位移关系就会被破坏,造成处理的低效;其次

还有一个更严重、更隐蔽的问题。如果要处理的图象太大,在内存中不能一次放入,这就需要在内存与

文件之间往复交换数据,当处理次序与存放次序不一致时,数据将会频繁地在内外存之间倒换,从而产

生抖动现象,即系统忙于为程序的正常运行准备内存空间而不停顿地交换内外存数据,反而使得程序无

法正常运行下去。如果我们在Windows上设计出一个产生抖动的程序,就会看到硬盘指示灯不断闪烁。如

果出现这种情况,就要对你的程序仔细检查,看是否有抖动的情况存在。
 

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值