• 以C语言为例的程序性能优化 --《深入理解计算机系统》第五章读书笔记


      其实大多数的编译器本身就能提供一些简单的优化,比如gcc就能通过使用 -O2 或者 -O3 的选项来优化程序。但编译器的优化始终也是有限,因为它必须小心翼翼保证优化过程不对程序的功能有改动。故而程序员本身应该对程序有优化意识。在我看来,这也是应该有的一种良好的编程习惯。

      几种比较简单的优化措施:

      1.代码移动

      将要执行多次(比如在循环中)但计算结果不会改变的计算,移动到代码前面不会多次求值的部分。举一个比较极端的例子:

    /* convert string to lowercase: slow*/
    void lower( char *s ){
        int i;
        for( i = 0;i < strlen(s);i++ )
            if( s[i] >= 'A' && s[i] <= 'Z' )
                s[i] -= ( 'A' - 'a');
    
    }

     因为C语言的字符串是以null结尾的,函数strlen也必须一步一步得检查这个序列,直到遇到null字符。那么假象一下,如果字符串s是一个很长的字符串,那么这个函数自然会造成许多不必要的开销!!
    故而在循环体内,要注意将计算结果不改变的计算移动到前面避免多次重复计算。

      优化代码:

    /* convert string to lowercase: faster*/
    void lower( char *s ){
        int i;
        int len = strlen(s);
        for( i = 0;i < len;i++ )
            if( s[i] >= 'A' && s[i] <= 'Z' )
                s[i] -= ( 'A' - 'a');
    
    }
    

      2.消除不必要的存储器引用

      在C语言中用指针变量读写是用CPU寄存器间接寻址然后从内存中读写,而使用函数内部的局部变量,则是使用CPU中的通用寄存器。而主存读写和CPU内部通用寄存器的寻址的速度相差数十倍的。举一个小例子

    for( i = 0;i < len;i++ ){
        *dest = *dest + data[i];
    }
    

     这个循环体每次都会从主存中读写,优化如下:

    int acc;
    for( i = 0;i < len;i++ ){
        acc = acc + data[i];
    }
    *dest = acc;
    

     这样就会使那个指针只写入一次,而acc变量在cpu的执行过程中是使用cpu内部通用寄存器读写,故而能加快速度。

      3.循环展开

      循环展开,顾名思义就是将一次一步的迭代循环展开成一次两步或更多,减少迭代次数。循环展开从两个方面改善程序的性能,首先,它减少了不直接有助于程序结果的操作的数量,比如循环索引计算和条件分支。其次,它提供了一些方法,可以进一步变化代码,减少计算中关键路径上的操作数量。比较如下两个函数,第一个为常规循环,第二个为循环展开函数,

    //normal function to add all element of v
    void
    combine1( vec_ptr v,data_t *dest ){ int i = 0; long int length = vec_length( v );
       data_t *data = get_vec_start( v );
       data_t acc = IDENT;
    for( i = 0;i < length;i++ ){ acc = acc + data[i]; } *dest = acc; }
    //unroll loop by 2
    void combine2( vec_ptr v, data_t *dest ){ int i; long int length = vec_length( v ); loing int limit = length -1; data_t *data = get_vec_start( v ); data_t acc = IDENT; for( i = 0;i < limit;i += 2 ){ acc = ( acc + data[i] ) + data[i+1]; } for( ;i < length;i++ ){ acc = acc + data[i]; } *dest = acc; }

     第二个函数将循环展开,并在最后检查会不会遗漏。减少了一些关键步骤,故而优化了程序。

      4.提高并行性

      在cpu中,程序被翻译成汇编指令,但却并不是一条一条指令按顺序执行的,而是流水线并发执行的,即多条不相关指令共同执行。这是cpu的机器特性,而我们要做的,就是多多利用这种机器特性。

      让我们来分析程序的combine2中的核心循环内部语句:acc = ( acc + data[i] ) + data[i+1];在这个循环中,data[i+1]的计算必须放在( acc + data[i] )之后,因为它们是相互关联的,这明显是不利于程序的并行操作,改进如下。

    //unroll loop by 2,2-way parallelism
    void combine3( vec_ptr v, data_t *dest ){
        int i;
        long int length = vec_length( v );
        loing int limit = length -1;
        data_t *data = get_vec_start( v );
        data_t acc0 = IDENT;
        data_t acc1 = IDENT;
        
        for( i = 0;i < limit;i += 2 ){
            acc0 =  acc0 + data[i];
            acc1 = acc1 + data[i+1];
        }
    
        for( ;i < length;i++ ){
            acc0 = acc0 + data[i];
        }
    
        *dest = acc0 + acc1;
    }
    

    这段代码将acc拆分成acc0和acc1,使程序得以并发同时计算,最后再将两组结果想加,提高程序性能。

     代码优化通常都会带来可读性的降低,如何取舍应该好好考虑清楚,必要时刻,或许应该多加一些注释说明。

  • 相关阅读:
    在spring MVC的controller中获取ServletConfig
    支付宝移动接入报系统繁忙,參数错误等错误
    Android:控件WebView显示网页
    android WebView总结
    关于用WebView或手机浏览器打开连接问题
    Android解决Fragment多层嵌套时onActivityResult无法正确回调的问题
    Fragment嵌套Fragment时遇到的那些坑
    Android 多个Fragment嵌套导致的三大BUG
    Android Fragment使用(四) Toolbar使用及Fragment中的Toolbar处理
    Android Fragment使用(三) Activity, Fragment, WebView的状态保存和恢复
  • 原文地址:https://www.cnblogs.com/listenfwind/p/5849030.html
Copyright © 2020-2023  润新知