from:http://just-study.blogbus.com/logs/37238127.html
注:本翻译来源于链接http://www.abarnett.demon.co.uk/tutorial.html
(PS,第一次翻译,有很多翻译有点可意会,不可言传的感觉:-),应该还是没有彻底理解文中的意思,见谅!)
转载请注明出处!
C代码优化教程
介绍
本文档已经经过反复修改,最终包含大量的方法帮助优化c语言代码。编译器是很好的,但是它们并不能按照你的意愿做所有事情,因此,我希望帮助大家从自己的代码中获取最好的性能比。本教程面向的不是初学者,而是更有经验的程序员。
声明
取决于你的特殊硬件环境和编译环境,下面列出来的一些技巧实际上可能使得你的代码运行更慢。就像现代的编译器一样,使用或者没有使用这些优化技术在低层次上都可能运行得很好。改善整体的算法能比局部代码优化取得更好的结果。本文档最初只是作为个人的一个备用笔记,并没有考虑成为C语言优化专题方面的权威性的文章。文章中我也可能存在一些错误,如果你有任何建议或者只是一些建设性的批评,与我取得联系。
编码速度
如何从你的C语言代码中获取最后的一点T-states呢???考虑的出发点应该是最快的执行速度而不是代码的可读性。
文章列出的源码没有进行语法检测,只是就事论事。当应用达到低级别的例程的时候,你应该已经过滤掉任何不良数据。
*数组索引
*别名
*寄存器
*整型数据类型
*循环干扰
*动态循环展开
*更快的for循环
*switch语句
*常指针使用
*提早退出循环
*杂项
1.使用数组索引
如果你希望根据某个变量的值变化把一个变量设置为一个特殊的字符(译注:其他的数据也行),也许你会这样做:
switch ( queue ){
break;
case1 : letter = 'S';
break;
case2 : letter = 'U';
break;
}
或者是这样:
if ( queue == 0 )
letter = 'W';
elseif ( queue == 1 )
letter = 'S';
else
letter = 'U';
然而一种更紧凑(并且更快)的方法是使用变量作为一个字符串数组的索引,上面的语句修改成这样的也许更好:
staticchar *classes="WSU";
letter = classes[queue];
2.别名
考虑下面的语句:
void func1( int *data )
{
int i;
for(i=0; i<10; i++)
{
somefunc2( *data, i);
}
}
尽管“*data”可能永远也不会改变,但是编译器不知道 somefunc2()没有改变它,因此,当该变量被使用的时候程序必须每次从内存中读取它--它也可能是一些在其他地方改变的变量的别名。如果你知道该变量不会被改变,你能按照下面的方法编码:
void func1( int *data )
{
int i;
int localdata;
localdata = *data;
for(i=0; i<10; i++)
{
somefunc2( localdata, i);
}
}
这样做的话将为编译器提供更大的机会进行优化。
3.×××数据类型和寄存器变量使用
如果你知道整数变量在之后的使用过程中不可能出现负数,那么你可以考虑使用无符号整数替代有符号正数.相对于有符号正数,一些处理器对无符号正数的算术运算处理速度更快.
因此,对一个应用于循环计数器的×××变量来说,最好的声明应该如下:
registerunsignedint var_name;
(虽然并没有机制保证编译器不会忽略"register"关键字,"unsigned"可能对一些处理器而言不会产生任何区别,但这样做确实是一种好的方法)
还有一点需要记住的,整型算术运算比浮点型算术运算要快很多.应为×××数据通常能被处理器直接处理,而浮点型数据需要依赖外部的FPUs单元或者外部浮点数学函数库.如果你的运算结果只需要精确到小数点后两位,运算前把所有的浮点型数据*100,到最后再转换成浮点型数据.
4.循环干扰
当一个循环足够的时候不要使用2个循环:
for(i=0; i<100; i++)
{
stuff();
}
for(i=0; i<100; i++)
{
morestuff();
}
下面的这种方式会更好写(生产的代码也要短)
for(i=0; i<100; i++)
{
stuff();
morestuff();
}
需要注意的是,然而,如果在循环中有大量的工作,它可能不能适合你的处理器指令缓存.在这种情况下,因为每一个循环能完全在缓存总运行,两个独立的循环实际上可能执行速度更快.
5. 循环展开和动态循环展开
众所皆知,展开循环能产生相当大的开销节省.例如:
for(i=0; i<3; i++)
{
something(i);
}
上面语句的效率就比下面语句的效率要低一些:
something(0);
something(1);
something(2);
因为第一种做法在每一次循环中都要检测循环条件和循环计数器i的自加.当循环中给出固定的循环次数,编译器常常能够自动展开第一种形式的循环成第二种形式.但是像下面的语句:
for(i=0;i<limit;i++){ ... }
就不太可能由编译器自动展开,因为我们并不知道循环迭代多少次.然而,展开这种循环是可能的,并从中节省一些时间开销.在"Graphic Gems"系列的书中就有这样的例子.这种方式不仅能加速图形渲染中行扫描的像素显示,而且可以适用于涉及到大量数据处理且操作方式相同的任何情况下.
下面的代码(列表1)的规模比一个简单的循环要大很多,但是,却执行效率更高.以8为块大小只是用来作为演示目的,任意合适的块大小都行--你只需要重复循环中的内容同样多次数.在下面的例子中,循环条件被设置为每8次重复调用然后才迭代执行一次,而不是函数调用一次迭代一次.假如你知道你要处理的数组的大小,你可以设置块大小为数组的大小(或者能够被数组大小所整除).然而我发现以8为块大小是一个很合适的大小.我在Sun Sparcstation上使用该优化技术进行一些性能测试,块大小为8或者16的时候效果很好,但是当我把块大小提高到32的时候,执行效率却下降了.(我认为这与机器的缓存大小有很大关系)
为对一张很大的单色位图(大概3000*2000像素点)进行求反操作,我在图像处理应用中使用的类似的代码,结果显示执行速度比使用简单的for()循环有显著的提高.因为处理器主要是专注于数据处理的工作,而不是循环次数的自加和循环条件的判断.
列表1
#include<stdio.h>
#define BLOCKSIZE (8)
void main(void)
{
int i = 0;
int limit = 33; /* could be anything */
int blocklimit;
/* The limit may not be divisible by BLOCKSIZE,
* go as near as we can first, then tidy up.
*/
blocklimit = (limit / BLOCKSIZE) * BLOCKSIZE;
/* unroll the loop in blocks of 8 */
while( i < blocklimit )
{
printf("process(%d)\n", i);
printf("process(%d)\n", i+1);
printf("process(%d)\n", i+2);
printf("process(%d)\n", i+3);
printf("process(%d)\n", i+4);
printf("process(%d)\n", i+5);
printf("process(%d)\n", i+6);
printf("process(%d)\n", i+7);
/* update the counter */
i += 8;
}
/*
* There may be some left to do.
* This could be done as a simple for() loop,
* but a switch is faster (and more interesting)
*/
if( i < limit )
{
/* Jump into the case at the place that will allow
* us to finish off the appropriate number of items.
*/
switch( limit - i )
{
case7 : printf("process(%d)\n", i); i++;
case6 : printf("process(%d)\n", i); i++;
case5 : printf("process(%d)\n", i); i++;
case4 : printf("process(%d)\n", i); i++;
case3 : printf("process(%d)\n", i); i++;
case2 : printf("process(%d)\n", i); i++;
case1 : printf("process(%d)\n", i);
}
}
}
另外一个简单的使用循环技巧是循环计数使用递减替代递增,以下面的代码为例,循环计数变量值从1递增到9:
for(i=0; i<10; i++)
{
do stuff...
}
如果循环执行顺序无所谓的话,用下面的方式替代:
for( i=10;i--;)
这种方式循环计数器从9递减到0.需要注意的是应该使用i--而不是--i,否则的话循环会提前终止.