《C程序性能优化》读后感

 


为什么读《C程序性能优化》?因为最近接连遭遇几次面试,都问到了语言底层的东西,比如寄存器变量等概念,让我意识到了语言底层的重要性。于是我想快速了解这方面的知识。鉴于《C程序性能优化》篇幅较短,就选择了它,并且用一天的时间就看完了,觉得收获了很多。

我觉得读这本书至少要会操作系统,因为感觉本书的提出的程序优化方法大致分为这几大类:增加cache命中率,并行化以及运算指令简单化(完全是自己归纳的,所以不太规范)。这每一大类都要求操作系等底层的知识,下面我依次从上述三个方面来讲讲C程序的优化。

首先来说说增加cache命中率,我尽量让我的文章简单一点,就只讨论一级cache了。书中最最经典的一个例子就是矩阵的乘法运算,如果假设矩阵按行存储的话,那么每次从cache中找不到矩阵的相应元素,就会从内存中读入这个元素以及离这个元素最近的元素(按行算,这是根据局部性原理)。如果我们能尽可能利用这条性质的话,就能减少访问内存的次数。比如,

for(i = 0;i <n;i++)

  for(j = 0;j<m;j++)

    for(k = 0;k<p;k++)

       c[i][j] += a[i][k] * b[k][j];

这里主要看看b[k][j],比如当前读入b[0][0],cache会自动把b[0][0-p](这里假设cache块的尺寸是p)都读进来,但是下次要用的是b[1][0], cache没命中,只能重新访存。这就比较麻烦了。所以对上面的程序做一下小改进,变成如下,

for(i = 0;i <n;i++)

 for(k = 0;k<p;k++)

  for(j = 0;j<m;j++)

       c[i][j] += a[i][k] * b[k][j];

上面所描述的问题就解决了。总结一下,我感觉就是尽可能让最频繁变化的下标对应我们的数组存储方式(这里是按行存储,所以列频繁变化,命中率是很高的)。

然后再来讨论并行化, CPU内部是有多个乘法计算器的,他们就是为了能同时进行乘法运算而存在的,但是我们在写程序的时候,一定要考虑到指令间的依赖关系,如果相邻的几条语句是没有依赖关系的,那么他们完全可以并行化执行,书中举了很多例子,这里我还是用上述例子来说明这个情况吧。代码修改为,

for(i = 0;i <n;i++)

 for(k = 0;k<p;k++)

{

  int a_i_k = a[i][k];

  for(j = 0;j<m;j+= 4)

       c[i][j +0] += a_i_k * b[k][j + 0];

       c[i][j +0] += a_i_k * b[k][j + 0];

       c[i][j +0] += a_i_k * b[k][j + 0];

       c[i][j +0] += a_i_k * b[k][j + 0];

}

性能再一次获得提升。

最后再来说说我眼中的运算指令简单化,个人电脑中使用的CPU,一般都有多个乘法,除法,赋值,加法等,赋值和加法快于乘法,乘法快于除法。所以我们在写程序的时候要尽可能少使用除法,用一些其他的操作来代替除法。比如我想进行如下操作,

a = 20000000;

for(i = 0;i < 1000000; ++i)

{

a = a/3;

}

每次都要除以常数3,最终这个程序用了21ms.

如果我修改成如下的代码,只用了10ms。

#define N 16

int a = 20000000;

start = clock();

int i;

for(i = 0;i < 1000000; ++i)

{

a = (a *((2<<N)/3))>>N;

}

这里实际上在编译的时候就得到了2<<N的值,然后先进行一次乘法,最后做一个位运算,就避免了使用除法。很巧妙。

书上其实还讲到了并行化的一些其他方面,比如SIMD中SSE,SSE使用了128位的存储单元,是可以存下4个32位的浮点数,SSE中的所有计算都是一次性针对4个浮点数来完成的,这种批处理当然就会带来效率的提升。要体现SSE的速度,必须有Stream做前提,就是大量的流数据,必须是以16位字节边界对齐的,这样才能发挥SIMD的强大作用。
总之,从这本书中,我收获了很多,很遗憾的是,由于时间太紧了,不能全部上机实践,推荐这本书!

 

posted @ 2018-04-19 20:48  eeom  阅读(234)  评论(1编辑  收藏  举报