CSAPP-Lab04 Architecture Lab 深入解析
穷且益坚,不坠青云之志。
1|0实验概览
Arch Lab 实验分为三部分。在 A 部分中,需要我们写一些简单的Y86-64
程序,从而熟悉Y86-64
工具的使用;在 B 部分中,我们要用一个新的指令来扩展SEQ
;C 部分是本实验的核心,我们要通过理解流水线的过程以及利用新的指令来优化程序。
实验材料中有一个archlab.pdf
,按照文档一步步往下走就可以了。make
时,可能会缺少相关依赖,安装如下软件即可
2|0Part A
在这部分,要用Y86-64
汇编代码实现examples.c
中的三个函数。这三个函数都是与链表有关的操作,链表结点定义如下
在编写汇编代码之前,我们先回顾一下Y86-64
的指令集:
2|1sum_list
本题就是一个链表求和,非常简单。但要注意,这里不仅要写出函数段,还应该写出测试的代码段。直接给出转换后的汇编代码:
注意,应在stack
下方空一行,否则汇编器会报错,报错原因我也不清楚。
利用实验文件中给的YAS
汇编器进行汇编,YIS
指令集模拟器运行测试
得到结果
返回值%rax=0xcba=0x00a+0x0b0+0xc00
,结果正确!
2|2rsum_list
这是链表求和的递归实现,按照C语言代码的过程模拟即可,思路非常清晰,可以参考我的注释
测试
结果正确!
2|3copy_block
数组赋值操作,返回值为原数组各项的按位异或
这段代码的架构与书上图 4-7的例子完全相同,包括常数的处理,循环的设置技巧,退出循环的判断... 照猫画虎即可,当然,我也在后面附上了注释
编译运行一下
结果完全正确
3|0Part B
Part B 整合了第 4 章的 homework - 4.51, 4.52。就是实现iaddq
指令,将立即数与寄存器相加。可以参考irmovq
和OPq
指令的计算。在开始之前,我们还是先回顾一下处理一条指令的各个阶段吧!
3|1回顾:指令处理框架
- 取址:根据 PC 的值从内存中读取指令字节
- 指令指示符字节的两个四位部分,为
icode:ifun
- 寄存器指示符字节,为
rA
,rB
- 8字节常数字,为
valC
- 计算下一条指令地址,为
valP
- 指令指示符字节的两个四位部分,为
- 译码:从寄存器读入最多两个操作数
- 由
rA
,rB
指明的寄存器,读为valA
,valB
- 对于指令
popq
,pushq
,call
,ret
也可能从%rsp
中读
- 由
- 执行:根据
ifun
计算,或计算内存引用的有效地址,或增加或减少栈指针- 对上述三者之一进行的操作得到的值为
valE
- 如果是计算,则设置条件码
- 对于条件传送指令,检验条件码和传送条件,并据此更新目标寄存器
- 对于跳转指令,决定是否选择分支
- 对上述三者之一进行的操作得到的值为
- 访存:顾名思义
- 可能是将数据写入内存
- 若是从内存中读出数据,则读出的值为
valM
- 写回:最多写两个结果到寄存器
- 更新 PC:将 PC 设置成下一条指令的地址
3|2iaddq指令执行过程
iaddq
的执行与Opq
非常相似,后者需要取出rA
与rB
分别指示的寄存器进行运算后再写回rB
指示的寄存器。而前者与后者唯一的区别就是,不需要从rA
中取数,直接立即数计算即可。
3|3修改HCL代码
接下来要在seq-full.hcl
文件中修改代码。由于iaddq
的操作与OPq
和irmovq
类似,比较取巧的做法是,搜索有这两个指令的描述块进行修改即可。本着学习的目的,我们分阶段对所有信号逐个分析
取指阶段
instr_valid
:判断指令是否合法,当然应该加上。修改后为
need_regids
:判断指令是否包括寄存器指示符字节,当然也应该加上
need_valC
:判断指令是否包括常数字,还是要加上
译码和写回阶段
srcB
:赋为产生valB
的寄存器。译码阶段要从rA
, rB
指明的寄存器读为 valA
, valB
,而iaddq
有一个rB
,于是有以下修改
dst_E
:表明写端口 E 的目的寄存器,计算出来的值valE
将放在那里。最终结果要存放在rB
中,所以要修改
执行阶段
执行阶段ALU
要对aluA
和aluB
进行计算,计算格式为:aluB OP aluA
。所以aluaA
可以是valA
和valC
或者+-8
,aluaB
只能是valB
。而iaddq
执行阶段进行的运算是valB + valC
,于是可知修改
set_cc
:判断是否应该更新条件码寄存器,这里应该加上
访存阶段
iaddq
没有访存阶段,无需修改
更新PC阶段
iaddq
不涉及转移等操作,也无需修改
3|4测试SEQ
编译失败处理办法
编译ssim
的时候出现了很多问题:
提示不存在tk.h
这个头文件,这是由于实验文件太老。把Makefile
修改一下。第 20 行改为
第 26 行改为
但是接下来还是报错了
这是因为较新版本glibc
弃用了这部分内容
解决办法是注释掉 /sim/pipe/psim.c 806、807 line
和 /sim/seq/ssim.c 844、845 line
。即:有源代码中有matherr
的一行和它的下一行
接下来就能编译成功了!虽然会有很多 Warning
测试
第一轮测试
运行一个简单的Y86-64
程序,并将结果ISA
模拟器的结果进行比对,输出如下
成功!
标准测试
运行一个标准检查程序
全部都是 Succeeds
回归测试
测试除iaddq
的所有指令
专门测试iaddq
指令
于是,我们就通过了实验材料中的所有测试用例!
4|0Part C
Part C 在sim/pipe
中进行。PIPE 是使用了转发技术的流水线化的Y86-64
处理器。它相比 Part B 增加了流水线寄存器和流水线控制逻辑。
在本部分中,我们要通过修改pipe-full.hcl
和ncopy.ys
来优化程序,通过程序的效率,也就是 CPE 来计算我们的分数,分数由下述公式算出
首先,iaddq
是一个非常好的指令,它可以把两步简化为一步,所以我们先修改pipe-full.hcl
,增加iaddq
指令,修改参考 Part B 即可。稳妥起见,修改后还是应该测试一下这个模拟器,Makefile
参考 Part B 部分进行同样的修改后编译。然后执行以下命令进行测试:
当所有测试都显示 Succeed 后,就可以真正开始本部分的重头戏了!
ncopy
函数将一个长度为len
的整型数组src
复制到一个不重叠的数组dst
,并返回src
中正数的个数。C 语言代码如下
原汇编代码如下:
先分别执行以下命令,对原始代码测试一波 CPE
得
4|1利用iaddq
首先能够直观看到,为了len--/src++/dst++
等操作,对%rdi
进行了不少次赋值操作,这些都可以用我们新增的iaddq
指令替代。
替代后代码为
测试 CPE
虽然分数还是0,但已经有了不少提升
4|2循环展开
根据文档的提示,可以试试循环展开进行优化。 循环展开通过增加每次迭代计算的元素的数量,减少循环的迭代次数。这样做对效率提升有什么作用呢?
- 减少了索引计算的次数
- 减少了条件分支的判断次数
那么展开几路效率最高呢?我从5路展开开始分别进行了测试
所以,我选择进行6路展开
代码逻辑非常简单:每次循环都对6个数进行复制,每次复制就设置一个条件语句判断返回时是否加1,对于剩下的数据每次循环只对1个数进行复制。
为了方便分析,我把极端的几个例子的情况列下来:
观察上表,对于小数据而言, CPE 的值非常大,后续可以考虑对小数据进行优化。我们先优化剩余数据的处理,对他们继续进行循环展开。
4|3剩余数据处理
对于剩余数据,我选择3路循环展开。前面的6路与上面代码一样,我就不再贴出来了
注意对于3路展开的特殊处理。看第38、39行,通过直接判断剩余数据的数量减少一次条件判断
CPE 值为
提升了很多,但是依然连一般的分数都还没拿到...
4|4消除气泡
注意,程序多次使用了下面的操作:
Y86-64
处理器的流水线有 F(取指)、D(译码)、E(执行)、M(访存)、W(写回) 五个阶段,D 阶段才读取寄存器,M 阶段才读取对应内存值,
即使使用转发来避免数据冒险,这其中也至少会有一个气泡。像这样
一个优化办法是,多取一个寄存器,连续进行两次数据复制。
像这样,对%r8
和%r9
进行读入和读出的操作之间都隔着一条其他指令,就不会有气泡产生了。代码如下:
注意,只有rmmovq
不改变条件寄存器的值,所以我们也可以把andq
插进中间来消除气泡。
CPE 值为
这一步的提升是巨大的!我的分数终于像点样子了!
4|5进一步优化
这里先留个坑。
暂且截图记录我目前为止的最高成就:
运行正确:
分数为:46.8
5|0总结
- 读 CSAPP 第 4 章时,我理解得很不通透,部分内容甚至有些迷糊。而做完了本实验,通过亲自设计指令,亲自模拟流水线的工作过程并思考如何优化,我对处理器体系结构有了更深的感悟,有一种了然于胸的感觉。
- CMU 的这两位大神老师 Randal E. Bryant 和 David R. O'Hallaron 简直令我佩服得五体投地。我本以为他们只是从理论层面上将第 4 章的处理器指令,流水线如何设计等等教授给我们。没想到,他们竟然真正设计实现了这样一套完整的
Y86-64
模拟器、测试工具供我们学习。本实验尤其是 Part C 每优化一次就能立即看到自己的分数,这犹如游戏闯关一般的体验令我着迷。这一切要归功于两位老师细致的设计,希望有生之年能见他们一次! - 作为一个完美主义者,我在 Part C 部分却没有拿到满分,这简直是无法忍受的。但是我着实学业繁忙,不能在这个实验耗费太多时间,只能暂且搁置,暑假回来继续干它!
- 本实验耗时 3 天,约 17 小时
__EOF__

本文链接:https://www.cnblogs.com/xvic/p/16002133.html
关于博主:可能又在摸鱼
版权声明:转载请注明出处
声援博主:如果看到我摸鱼请赶我去学习
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!