zoukankan      html  css  js  c++  java
  • 以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,使程序得以并发同时计算,最后再将两组结果想加,提高程序性能。

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

  • 相关阅读:
    Allegro PCB Design GXL (legacy) 使用slide无法将走线推挤到焊盘的原因
    OrCAD Capture CIS 16.6 导出BOM
    Altium Designer (17.0) 打印输出指定的层
    Allegro PCB Design GXL (legacy) 将指定的层导出为DXF
    Allegro PCB Design GXL (legacy) 设置十字大光标
    Allegro PCB Design GXL (legacy) 手动更改元器件引脚的网络
    magento产品导入时需要注意的事项
    magento url rewrite
    验证台湾同胞身份证信息
    IE8对css文件的限制
  • 原文地址:https://www.cnblogs.com/listenfwind/p/5849030.html
Copyright © 2011-2022 走看看