Csapp读书笔记:第五章

来源:互联网 发布:配置ubuntu镜像站 编辑:程序博客网 时间:2024/06/01 08:15

(1)安全优化,编译器在进行优化的时候必须考虑所有的情况,只执行安全的优化。所以可能会限制很多可能的优化策略。

这里写图片描述

如果写出上面这样的代码,当xp==yp的时候,最后的结果等于0。否则xp和yp指向的对象进行了一次交换。由于有两种可能的情况,限制了编译器可能进行的优化.

另外函数的调用改变了全局变量也可能造成优化限制。使用内联函数可能可以避免这种限制,有些编译器在较高的优化级别也可能会进行内联函数替换的优化。

(2)

简单来说就是把一些在循环中不变的量提前计算出来,这种优化很常见。比如下面提前计算长度。由于编译器不能发现一个函数是不是有副作用,所以它假定有副作用,所以不会去主动优化。同理将某个结构体或者类的变量名提取到局部变量会使得编译器可以从寄存器取得值,这样也会加快程序。

这里写图片描述

(3)减少过程的调用,直接对过程的内容进行变换移植到程序里面,当然这会破环程序的模块化以及抽象性。具体就要看自己的取舍了。

(4)消除不必要的存储器调用。

这里写图片描述

类似上面的程序会产生下面这样的汇编:

这里写图片描述

可以发现这样会使得程序每次都要访问相关的存储器,这是很慢的。解决方法是使用一个临时的变量来进行累积的运算,最后再加载到dest上面。

那么为什么编译器不主动进行这样的优化呢?实际上这涉及到安全的问题,两种计算方式可能会带来不同的结果,因为v和dest可能会有重叠,比如dest指向v的最后一个元素,这样两种方式最后的结果就会完全不一样了!

这里写图片描述

这样我们一定要注意可能出现的错误情况,第二种方法不但优化程序还可以避免可能的错误,所以值得推荐。

(5):

下面是GCC开-O2后对combine3产生的汇编代码:

这里写图片描述

可以发现编译器作了一个巧妙的优化,等价于以下代码:而且这个变换对于v和dest指向同一个地方时也不会影响到原来的代码意思。(当然原来的代码的意图很可能是一个bug)

这里写图片描述

(6) 循环展开,多路并行,分支预测等技术就不细说了,编译器基本都已经帮你做好了很多。值得注意的是当数据无法预测时,应该避免if,直接计算出值。比如下面这个例子:

如果a,b的大小是随机的,那么本质上是不可以预测的,所以一定有很多预测错误,这样代价很昂贵。所以我们最好用vec4的方式。

void vec3(vector<data_type>a,vector<data_type>b){    auto L = a.size();    for (size_t i = 0; i < L; i++){        if (a[i] > b[i]){            auto t = a[i];            a[i] = b[i];            b[i] = t;        }    }}void vec4(vector<data_type>& a, vector<data_type>& b){    auto L = a.size();    for (size_t i = 0; i < L; i++){        auto x = a[i], y = b[i];        a[i] = x< y ? x : y;        b[i] = x> y ? x : y;    }}

但是也不能盲目使用上面的风格,首先会牺牲可读性。其次也不一定有效果。比如书上的5.9我在vs2013上,release模式下测试发现避免if的函数效率还差一些:这可能是编译器作了一些我们不知道的优化导致的。也可能使避免if的计算代价比较大。

void merge(vector<data_type>& a, vector<data_type>& b, vector<data_type>& dest){    auto i1 = 1, i2 = 1,i = 0;    auto n = a.size();    while (i1< n && i2< n)    {        if (a[i1] < b[i2]){            dest[i++] = a[i1++];        }        else{            dest[i++] = b[i2++];        }    }    while (i1 < n){        dest[i++] = a[i1++];    }    while (i2 < n){        dest[i++] = b[i2++];    }}void merge2(vector<data_type>& a, vector<data_type>& b, vector<data_type>& dest){    auto i1 = 0, i2 = 0, i = 0;    auto n = a.size();    while (i1 < n && i2 < n)    {        auto v1 = a[i1], v2 = b[i2];        auto take1 = v1<v2;        dest[i++] = take1 ? v1 : v2;        i1 += take1;        i2 += (1 - take1);    }    while (i1 < n){        dest[i++] = a[i1++];    }    while (i2 < n){        dest[i++] = b[i2++];    }}

习题:

5.15

这个题目只要找到关键路径就可以了,经过分析发现只有sum的加法更新是处于关键路径上面的,而乘法由于不在关键路径上面,因此可以利用流水线来处理,那么真正影响CPE的就是加法的延迟了。

5.16每一次运算都需要执行两个load,所以CPE不可能低于2.0。同时关键路径上的浮点数加法也没有变化,所以循环展开不能降低CPE。

5.19这里的实现参考了网上的例子,利用了较长的字进行写入。同时要注意字节对齐。

void *optimized_memset(void *s, int c, size_t n){    unsigned int K = sizeof(unsigned long);    unsigned char *schar = (unsigned char*)s;    unsigned long *lchar;    unsigned long fill = 0;    int i = 0;    for (i = 0; i < K; i++)        fill += (c & 0xff) << (i << 3);//fill的K个字节都是c的低位    while ((unsigned)schar%K && n)//让schar的地址成为K整数倍    {        *schar++ = (unsigned char)c;        n--;    }    lchar = (unsigned long*)schar;    while (n >= K) {        *lchar++ = fill;//每次写入k字节        n -= K;     }    schar = (unsigned char*)lchar;    while (n) //剩余的n    {        *schar++ = (unsigned char)c;        --n;    }    *schar = '\0';    return s;}
0 0
原创粉丝点击