《深入理解计算机系统》—优化程序性能

程序一般从三个方面优化性能:①算法和数据结构②使编译器更好地优化③并发


程序优化应该将精力放在少量的核心函数的循环上。重点优化那些调用次数比较频繁的函数。


消除循环的低效率:

(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路并行变换。
这个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;
}

按照列取数据,那么缓存命中率会急剧下降。








你可能感兴趣的:(《深入理解计算机系统》—优化程序性能)