深入理解计算机系统——第五章小结(1)

中午装Server 2008, 顺便找来CS:APP看了一会。以后弄创新杯的事情,中间抽空看一看吧。

5.1-5.7 描述了一个内容,即通过代码优化来不断减少CPE(cycles per element, 每元素的周期数)度量

 
首先要理解CPE 
例如 1.4GHZ表示CPU频率 1400兆赫兹,而一个2GHZ的CPU时钟周期为0.5纳秒。

详细的解释看这里:http://www.seenthewind.com/?p=1088
下面两个程序

void vsum1(int n)
{
    int i;
    for (i = 0; i < n; i++)
       c[i] = a[i] + b[i];
}


void vsum2(int n)
{
    int i;
    for (i = 0; i < n; i+=2) {
      c[i] = a[i] + b[i]
      c[i+1] = a[i+1] + b[i+1]
    }
}

其中,vsum1大约消耗 80+4.0n,而vsum2大约消耗83.5+3.5n。 即 80~84的常数周期加上 3.5或者4.0的线性因子。

而这时候,我们我们的CPE定义为,vsum1为 4.0, vsum2为3.5.


接下来谈一谈代码优化。

1. 消除循环的低效率。

    for (i = 0; i < vec_length(v); i++){

    // ....
    }

    将vec_length(v)移出循环式,改为常量,我们一般成为代码移动(Code motion)。可以降低10~20的CPE.
    也许你会觉得并不太明显,但是如果是

    for (i=0; i < serlen(s); i++) {
    // ....
    }

    这样的话,在长度为262144的字符串时,整整需要3.1分钟完成对字符串的大小写反转,而使用常数只需要0.006秒。

2. 减少调用过程

    如果出现在循环中调用检查是否越界的函数。如:

    for (i = 0; i < length; i++) {
    //....

    get_vec_element(v, i, &val); // 获得v向量中第i位元素,存入val
    
    }

    可以改为

    data_t *data = get_vec_start(v);

     for (i = 0; i < length; i++) {
    //....

    *dest = *dest OPER data[i;]
    
    }

    虽然看起来会有些损害模块性和抽象性,但是改进最高可以达到3.5X(即3.5倍,X表示相对性能 = Told/Tnew)

3. 消除不必要的寄存器引用。

    开启 -o2开关时,编译器会主动帮助你优化代码,但是大部分时候他会严格保证程序的正确执行,包括某些特殊情况
    (比如你想让两个指针指向的值相加,但有时你传入了相同的指针)。

    这种时候我们需要稍微研究机器码,分析不必要的寄存器引用,可以优化代码。

    例如

    movl (%edi), %eax
    imull (%ecx, %edx, 4), %eax
    movl %eax (%edi)
    incl %edx
    .....

    截取自以下代码的一部分:
    for (i = 0; i < length; i++) {
    // ....
    *dest = *dest OPER data[i];

    }

    汇编指令1,3对存放在dest中的值频繁读写,看上去是种浪费,所以我们改为:

    data_t x = IDENT;
    for (i = 0; i < length; i++) {
    // ....
    x = x OPER data[i];

    }
    *dest = x;


   得到的汇编码:
    imull (%eax, %edx, 4), %ecx
    incl %edx


    这样的好处在 data_t 类型为浮点数的时候尤其明显。CPE下降了100~120。

    将会在下一小结继续讨论这个问题。


此条目发表在C, 编程, 读书分类目录,贴了, 标签。将固定链接加入收藏夹。

发表评论

邮箱地址不会被公开。 必填项已用*标注