he 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 ) { case 7 : printf("process(%d)n", i); i++; case 6 : printf("process(%d)n", i); i++; case 5 : printf("process(%d)n", i); i++; case 4 : printf("process(%d)n", i); i++; case 3 : printf("process(%d)n", i); i++; case 2 : printf("process(%d)n", i); i++; case 1 : printf("process(%d)n", i); } } return 0; }
计算非零位的个数 / counting the number of bits set
例1:测试单个的最低位,计数,然后移位。
//example1
int countbit1(uint n) { int bits = 0; while (n != 0) { if(n & 1) bits++; n >>= 1; } return bits; }
例2:先除4,然后计算被4处的每个部分。循环拆解经常会给程序优化带来新的机会。
//example - 2
int countbit2(uint n) { int bits = 0; while (n != 0) { if (n & 1) bits++; if (n & 2) bits++; if (n & 4) bits++; if (n & 8) bits++; n >>= 4; } return bits; }
尽早地退出循环 / Early loop breaking
通常没有必要遍历整个循环。举例来说,在数组中搜索一个特定的值,我们可以在找到我们需要值之后立刻退出循环。下面的例子在10000个数字中搜索-99。
found = FALSE; for(i=0;i<10000;i++) { if(list[i] == -99) { found = TRUE; } } if(found) printf("Yes, there is a -99. Hooray!n");
这样做是可行的,但是不管这个被搜索到的项目出现在什么位置,都会搜索整个数组。跟好的方法是,再找到我们需要的数字以后,立刻退出循环。
found = FALSE; for(i = 0; i < 10000; i++) { if( list[i] == -99 ) { found = TRUE; break; } } if( found ) printf("Yes, there is a -99. Hooray!n");
如果数字出现在位置23上,循环就会终止,忽略剩下的9977个。
函数设计 / Function Design
保持函数短小精悍,是对的。这可以使编译器能够跟高效地进行其他的优化,比如寄存器分配。
调用函数的开销 / Function call overhead
对处理器而言,调用函数的开销是很小的,通常,在被调用函数所进行的工作中,所占的比例也很小。能够使用寄存器传递的函数参数个数是有限制的。这些参数可以是整型兼容的(char,short,int以及float都占用一个字),或者是4个字以内的结构体(包括2个字的double和long long)。假如参数的限制是4,那么第5个及后面的字都会被保存到堆栈中。这会增加在调用函数是存储这些参数的,以及在被调用函数中恢复这些参数的代价。
int f1(int a, int b, int c, int d) { return a + b + c + d; } int g1(void) { return f1(1, 2, 3, 4); } int f2(int a, int b, int c, int d, int e, int f) { return a + b + c + d + e + f; } ing g2(void) { return f2(1, 2, 3, 4, 5, 6); }
g2函数中,第5、6个参数被保存在堆栈中,在f2中被恢复,每个参数带来2次内存访问。
最小化参数传递的开销 / Minimizing parameter passing overhead
为了将传递参数给函数的代价降至最低,我们可以:
尽可能确保函数的形参不多于四个,甚至更少,这样就不会使用堆栈来传递参数。
如果一个函数形参多于四个,那就确保在这个函数能够做大量的工作,这样就可以抵消由传递堆栈参数所付出的代价。
用指向结构体的指针作形参,而不是结构体本身。
把相关的参数放到一个结构里里面,然后把它的指针传给函数,可以减少参数的个数,增加程序的可读性。
将long类型的参数的个数降到最小,因为它使用两个参数的空间。对于double也同样适用。
避免出现参数的一部分使用寄存器传输,另一部分使用堆栈传输的情况。这种情况下参数将被全部压到堆栈里。
避免出现函数的参数个数不定的情况。这种情况下,所有参数都使用堆栈。
叶子函数 / Leaf functions
如果一个函数不再调用其他函数,这样的函数被称为叶子函数。在许多应用程序中,大约一半的函数调用是对叶子函数的调用。叶子函数在所有平台上都可以得到非常高效的编译,因为他们不需要进行参数的保存和恢复。在入口压栈和在出口退栈的代价,跟一个足够复杂的需要4个或者5个参数的叶子函数所完成的工作相比,是非常小的。如果可能的话,我们就要尽量安排经常被调用的函数成为叶子函数。函数被调用的次数可以通过模型工具(profiling facility)来确定。这里有几种方法可以确保函数被编译成叶子函数:
-
不调用其他函数:包括那些被转换成调用C语言库函数的运算,比如除法、浮点运算。
-
使用关键字__inline修饰小的函数。
内联函数 / Inline functions
对于所有调试选项,内嵌函数是被禁止的。使用inline关键字修饰函数后,跟普通的函数调用不同,代码中对该函数的调用将会被函数体本身代替。这会使代码更快,另一方面它会影响代码的长度,尤其是内嵌函数比较大而且经常被调用的情况下。
__inline int square(int x) { return x * x; } #include
double length(int x, int y){ return sqrt(square(x) + square(y)); }
使用内嵌函数有几个优点: