欢迎光临散文网 会员登陆 & 注册

CSAPP LAB之archlab,pipeline、循环展开、CPE=7.40

2023-06-23 15:17 作者:科G栈  | 我要投稿

此lab主要是针对CSAPP第四章的内容,主要是学习设计和优化Y86-64处理器,分成了3个部分,第一部分熟悉Y86-64指令,第二部分熟悉pipeline的设计,第三部分结合前两部分综合优化,获得尽可能低的CPE。

优化建议

我相信大部分人都是来看partC的优化的,所以先说说优化思路,按着思路再自己试试,实在想不出来再看答案。

  1. 必须添加iaddq指令;

  2. 此lab,%rax默认为0,不需要xor;

  3. 循环展开;

  4. 有数据冒险,可以通过psim -g调试发现冒险的地方,插入其他指令规避;

  5. 有控制冒险,要合理安排跳转;

  6. 一个测试条件可以同时跟jl、je、jg;

  7. 着重优化非循环部分/数据少的部分;

  8. 需要用到三叉树;

  9. 剧透一下,hcl文件只改iaddq,通过优化ncopy代码是可以得满分的。

进阶优化:

  1. 修改hcl文件,解决掉数据冒险,可以看书的家庭作业4.57。

配置问题

或者是环境配置问题,毕竟lab很老了,很多东西都更新了,可以根据出现的错误提示一步步解决。

1、可能需要安装tcl、tk、flex、bison

2、可能要修改Makefile

Makefile中的TKINC的tcl头文件路径是tcl8.5,目前系统下载的是8.6,以后可能还会更新,改成对应的版本号。

3、可能代码发生了改变

tcl的代码结构发生了变化,改变了使用方法,不过可以添加-DUSE_INTERP_RESULT到Makefile中解决,忽略相关的警告。

4、可能提示找不到变量matherr

把它注释掉就好,

第一部分

将examples.c中的三个函数改写成Y86-64汇编程序,这个不难,没啥好说的,直接上代码吧。

sum.ys

rsum.ys

copy_block.ys

第二部分

添加iaddq指令,目的是熟悉hcl语言,后续要用到iaddq。具体改动看下面的diff,这个也不算难,理解了hcl可以很容易搞定。


第三部分

这部分才是这个lab的重头戏,通过修改ncopy.ys和pipe-full.hcl让ncopy.ys跑的尽可能快,几个要求是:

  1. ncopy要适用任意数组大小;

  2. ncopy要能过YIS;

  3. ncopy的目标文件不能大于1000字节;

  4. pipe-full.hcl要能过../y86-code 和 ../ptest的测试,尤其是这点,benchmark不检查对错,如果psim有问题,benchmark可能会得到很低的CPE,误以为自己得了满分。

3.1、代码和模拟器测试:

1、保证代码正确:

./correctness.pl

2、保证模拟器正确

(cd ../y86-code; make testpsim)

(cd ../ptest; make SIM=../pipe/psim TFLAGS=-i)

3、保证二者结合正确

./correctness.pl -p

通过了上面的测试,才能保证代码和模拟器都OK。

运行./benchmark.pl查看最终的CPE和得分。

3.2、原始ncopy.ys和pipe-full.hcl得分

3.3、hcl添加iaddq指令,ncopy.ys改为iaddq指令,速度明显提升;

3.4、循环展开

到此,需要进一步对代码动手了,方法是第五章的循环展开,由于目标代码长度1000bytes的限制,最多只能展开到10。另外,针对此lab,%rax默认为0,可以去掉xorq %rax,%rax这句,不过实际应用不能这样。

刚开始的思路是先展开(8-10)次循环,剩下的展开4次循环,之后2次,最后1次,(简称n421,以后都这么叫)通过判断rdx选择执行的路径组合,展开10次(10421)的CPE能到7.69,提升明显。代码见附1。

对于以上结果不满意,复制3个数据会分成2+1,这比较慢,想想改为n4321,3也独立出来,这次展开8次最快,CPE到7.60。代码见附2。

仍然不满意,4以下的路径是3-2-1-0,

下图可知1的权重很大,减少它的CPE很关键。

 

汇编可以一个条件执行多个跳转,而跳转有小于(jl)、等于(je)和大于(jg),

所以可以用三叉树构建跳转路径,减少指令数。

因为处理器有分支预测,并且默认是要跳转的,所以要把概率大的或者紧要的部分紧挨着测试条件,因为一旦预测错误,就会多出两条bubble。按照上面的树,构造出n4r213(r2表示root2,2是根,左在前,右在后,0第一个不算CPE,永远放到分支最后,而且可能会跟10冲突,故省略),我测试84r213最快,得到下面的结果,可见优化有效,CPE已经非常接近7.5了。这里还可以以1为根,感兴趣的小伙伴可以自己测试,看看是快是慢。代码见附3.

就差1.1就满分了,继续努力。我们发现循环展开部分已经无法优化了(最多是8、9、10的差别),后边1、2、3也到头了,问题就在中间的4了。之前的代码都是小于4的直接执行0、1、2、3的部分,大于等于4的先执行4次展开,再根据结果执行0、1、2、3,这里就拖慢了性能,我们换个思路进一步优化。

 

这次我们把非循环部分都独立出来,用上面树的思想得到具体的数,然后直接跳到对应的地方执行,如下图所示。这里以3为根,原因还是要尽量靠近循环次数少的,尽可能减少其指令数。(CPE=指令数/数据个数,数据越少,非复制部分占比越大。这里的定义好像跟第五章的CPE有点差别,我们知道这里啥意思就可以了)。

分支部分的代码如下所示(9r31264578):

上图右边只是示意图,具体实现还需要优化,因为mrmovq和rmmovq 如果前后挨着,会有装载/使用冒险,第二条rmmovq指令会产生一个暂停,这会降低性能,可以使用加载转发技术,修改hcl文件解决这个问题,不过我们先不动hcl文件,想办法在它们之间插入一条其他指令,取代这个暂停就好,具体办法是把上一条指令的判断正负插入他们中间,如下:(可读性非常差,一不小心就混乱了,一定要仔细)

通过以上努力,我们得到了最终的版本,实测展开9次和10次CPE=7.49,满分啦。代码见附4。


到此,只增加iaddq指令的优化就做到这里了,或许还能提高,就交给感兴趣的小伙伴自行尝试吧。

3.5、加载转发

之前的版本虽然满分但还是有缺陷,或者是不可避免的会有一个暂停(如下图,rdx为8),

或者是执行一条没用的跳转(如下图,rdx为7,跳到这里),


这都是装载/使用冒险带来的副作用,看来必须把它解决掉才能得到完美的版本。加载转发具体原理可以看原书家庭作业4.57,我们在添加iaddq指令的pipe-ful.hcl文件中进一步做以下修改:

重新make并测试,都OK就得到了改进版的psim。

这样在ncopy里写代码就没有之前的限制了,mrmovq和rmmovq可以挨着,没有暂停。这里我人为加了个要求,就是寄存器只用调用者保存的,因为实际写程序,用被调用者保存寄存器是需要入栈和出栈的,这里虽没有要求,但规范还是要的。

此时的性能瓶颈就只在分支的安排上了,展开越多,分支的可能性越多,脑子想哪个更快难度很大,具体效果还需要实验,我实测展开10次最好,分支是10r312645789,CPE=7.40,这已经超过文档中老师的最好7.48,其他分支情况可能是7.44、7.45、7.46,差距不是太大。我都怀疑自己是不是弄错了,希望小伙伴们也试试,看能否复现。

最优版本ncopy代码如下:

最优版本pipe-full.hcl如下:

这里或许没人看,我就说点悄悄话,我们通过上面的过程可以看到,修改指令和循环展开获取的性能提升非常明显,这些优化是显而易见的,也是比较容易的,性价比很高。而后续的优化包括分支优化、加载转发,提升不算大,但是难度却不小,花了大量的时间,CPE的提升只有0.1,性价比不高。

不过,容易的谁都能做到,而能把不容易的做到,才能体现出自己产品的优势。很多好的系统和处理器或许就是在这些细节上有精益求精的要求,日积月累下让这些小的优势积累成了大的壁垒,成为了自己产品的核心竞争力。个人认为不应该忽视这些细枝末节的优化,累积的多了就会成为突变。

俗话说:不积跬步无以至千里,或许就是这个道理。

附1:10421

 

附2:84321

 

附3:84r213

 

附4:满分-只改iaddq指令

 

 


CSAPP LAB之archlab,pipeline、循环展开、CPE=7.40的评论 (共 条)

分享到微博请遵守国家法律