您的位置:首页 > 运维架构 > Linux

linux编程的108种奇淫巧计-4(编译展开)(续)

2010-10-30 09:09 239 查看
编译展开的这篇博客被CSDN推了首页http://blog.csdn.net/pennyliang/archive/2010/10/28/5971059.aspx,有些读者反映有些太难,考虑到有些地方没有讲得太清楚,本文一并进行深入讨论。

首先关键的代码是:

#define DO(x) x
#define DO4(x) x x x x
#define DO8(x) DO4(x) DO4(x)
#define DO16(x) DO8(x) DO8(x)

读者可以按葫芦画瓢,继续展开,这并不难理解。



我们选取了计算斐波那契数作为例子,将代码展开为16的倍数,但是因为Fx是用户输入的,可能是16的倍数也可能不是,因此需要做一些转换,将Fx变为Fx=16idx+r,这样可以确保可以做idx次展开,最后尾部再用一个循环计算完,如下:

int r= Fx%16;
int idx = Fx/16;
int i=2;
for(;i<idx;)
{
DO16(F[i]=F[i-1]+F[i-2];i++;); //展开成16段代码
}
for(;i<Fx;i++)
{
F[i]=F[i-1]+F[i-2];
}
如果我们不用编译来做循环展开,我们的代码可能就得是

for(;i<idx;)
{
F[i]=F[i-1]+F[i-2];i++;

F[i]=F[i-1]+F[i-2];i++;

F[i]=F[i-1]+F[i-2];i++;

....写16遍相同的代码,多难看啊。
}
for(;i<Fx;i++)
{
F[i]=F[i-1]+F[i-2];
}
第二,我们要特别注意memset的使用,memset在计时之前进行,是因为这样的计算更为准确,malloc分配大内存是采用mmap的方式,即只分配虚地址,而没有实际的调页,而我们做一个memset是为了调页,大家可以做这样的实验,做两次相同的malloc,用rdtsc的方式进行计时,你会发现第一次malloc会慢一些,而第二次会快,因为第一次有调页,而第二次几乎无调页(内存要足够大,否则可能会swap,也可能有少量调页)。

用memset相信也不难理解,因为这个时间混在里面可能会看不出误差,假定某市一季度GDP为10,二季度为15,看上去提高了50%,但是因为在计算过程中,有5份的GDP(可以想象成memset的代价)是多算在里面的,如果扣除这5份,则实际上是从5提升了100%。因此我们在设计实验时,需要把一些干扰项扣除,来比较,实验的数据才具有价值。



第三,我的实验结果是展开为4次是比较快的,我认为有这样一些主要原因,但还需要设计进一步的实验来证明:

(1)编译展开的层次过大,代码会变大,而代码存储在文件中,进入执行需要有一个读磁盘的过程,另外代码大,代码局部性就不强,L1 cache的一部分是存放代码段的,如果代码越小越紧凑,那么执行起来就会越开,展开出1024层,代码的局部性肯定很差。

(2)编译展开的层次过小,代码虽然紧凑,但是流水线不通畅,跳转太多,因此也容易导致性能变低。



因此总会有一种展开的层次可以trade off的流水线和代码段的紧凑性,我的实验结果是展开到4层是最快的,这是受机器环境影响的,不知道其他同学的实验结果是怎样的。



最后提到的细节是DO16(F[i]=F[i-1]+F[i-2];i++;); 的最后一个分号是不必要的,但是添加在这里是为了让代码更自然,相信大部分细心的读者都能看到这一点。



本系列其他文章:http://blog.csdn.net/pennyliang/category/746545.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: