程序一般从三个方面优化性能:①算法和数据结构②使编译器更好地优化③并发
程序优化应该将精力放在少量的核心函数的循环上。重点优化那些调用次数比较频繁的函数。
消除循环的低效率:
(1)可以在循环的时候避免一些重复性的判断(代码移动,将不必要的计算移动到循环外),例如:
for(i = 0; i < get_length(vec); ++i)
{/**/}
这个代码的内部每次循环判断都会进行重复计算get_length(vec),下面的优化会提高很多效率:
int length = get_length(vec);
for(i = 0; i < length; ++i)
{/**/}
这种优化有的编译器可以帮助完成,但是当编译器无法判断get_length()函数是否有副作用的时候,就不会进行上述的优化,所以最好还是自己进行。
(2)减少函数的调用
函数调用会伴随着上下文切换的开销,所以如果一个函数的功能可以直接以表达式进行,就能优化效率(与程序的模块化,可读性进行权衡)
(3)消除不必要的存储器引用
适当引入临时变量可以充分利用寄存器的特性,例如以下代码:
for(i = 0; i < length; ++i){
*dest = *dest + data[i];
}
每次循环都伴随着两次读和一次写(寄存器中存的是dest地址),可以作以下优化:
int acc = 0 ;
for(i = 0; i < length; ++i){
acc += data[i];
}
*dest += acc;
这样,变量acc会被存在寄存器中,每次循环只需要从内存中取data[i]即可。
tips:编译器不会进行上述的优化,因为可能存在另外的一个指针指向*dest的内容(编译器的优化一定是以不影响计算结果为首要准则的。)
(4)循环展开
通过增加每次迭代计算的元素数量,减少循环的迭代次数,例如:
for(i = 0; i < length; ++i)
{/*do*/}
这样的代码可以改进为:
for(i = 0; i < length; i+=2)
{
/*do 1*/
/*do 2*/
}
这是一个二路并行的例子,类推到k路并行变换。
寄存器溢出:指令集中只有很少的寄存器可以使用,如果并行度超出了寄存器的数量,就会将一些本该由寄存器保存的临时值存放在栈中,此时的性能就会急剧下降,栈的存取速度远小于寄存器的存取速度(寄存器在CPU中,栈在内存中)。
编写高速缓存友好的代码
使自己的程序具有好的局部性,包括时间局部性和空间局部性。
时间局部性:尽量使用局部变量替代函数参数变量,并且每次循环都在这些局部变量上进行计算。这些局部变量会被编译器缓存在寄存器文件中。
空间局部性:要充分考虑数据在内存中的存储形式,尽量使每次循环都使用的是相邻的数据,这样可以降低程序的缓存不命中率。举例:
int func(int** num, int m,int n)
{
int i,j,sum = 0;
for(i = 0; i < m; ++i){
for(j = 0; j < n; ++j){
sum += num[i][j];
}
}
return sum;
}
在上述函数中,使用了临时变量i,j,sum他们都会存在寄存器中,每次循环使用的变量从相同的寄存器中取,所以程序的时间局部性很好。
对于二维数组,内存中的存储形式是按照行连续存储的,所以每次的num[i][j]都是存储在相邻的位置,但是如果程序按照以下的方式进行计算:
int func(int** num, int m,int n)
{
int i,j,sum = 0;
for(i = 0; i < m; ++i){
for(j = 0; j < n; ++j){
sum += num[j][i];
}
}
return sum;
}