简介《计算机系统结构张晨曦版》课后习题参考答案以doc文档格式整理面向计算机专业本科生、考研复习者及自学该课程的读者帮助逐题核对知识点与解题过程。压缩包内包含1个doc文件整体约170KB内容聚焦教材第1章“计算机系统结构的基本概念”覆盖层次结构、虚拟机与翻译/解释、计算机组成与实现、Amdahl定律、程序局部性原理、CPI、Flynn分类法、并行性等级等核心术语的释义并给出主存系统设计实例、定量原理说明及一道主频400MHz机器的CPI、MIPS与执行时间计算题的完整解答可作为课堂学习与期末备考的辅助资料。资料包目前已有1379人学习使用适合同步对照教材章节查漏补缺强化对系统结构基础概念和计算方法的掌握。1. 为什么要把这份课后答案当作性能工程的模型库来读很多人拿到张晨曦版《计算机系统结构》的课后答案第一反应是照着抄完作业就扔了。但如果你做过几年性能优化会意识到这份材料里的习题其实是麻雀虽小五脏俱全从 Amdahl 定律到 CPI 计算从流水线冲突到向量链接几乎每个题都能映射到真实 CPU 设计或业务系统调优中的决策点。我自己的习惯是拿到一份课后题先不看答案而是把每道题当作一个“系统参数已知求性能边界”的小实验这比背概念有用得多。这份答案的独特之处在于它把术语定义和量化计算放在一起比如 1.6 题直接给你指令分布让你算有效 CPI1.8 题又让你反过来解可改进比例这种“正推 反推”的练习恰好是性能工程里最常用的能力。适合三类人正在复习考研 408 或计算机统考的在校生准备体系结构面试的工程师以及需要快速回顾性能模型的产品或运维同学。下面我按自己的拆解路径把这份答案里的核心知识点重构成能直接上手的分析工具。2. 从术语表到量化模型先搭建系统结构的概念坐标系2.1 翻译、解释、模拟、仿真四个执行模型不能混很多初学者把“翻译”和“解释”当成一回事又把“模拟”和“仿真”混着说。课后答案 1.1 给出的定义其实非常精确区别在于“转换发生的时间”和“实现载体”。我把它们整理成表执行模型实现媒介典型场景关键特征翻译转换程序编译器把高级语言转成机器码先整体转换再执行低一级程序解释低一级等效程序Python/Java 解释器逐条执行每取一条指令就转去执行一段低一级程序模拟宿主机上的软件QEMU 模拟另一套指令集用软件方法在宿主机上实现虚拟机指令系统仿真宿主机微程序硬件仿真器验证芯片设计用微程序解释目标机指令系统速度更快但依赖硬件翻译的核心是一次性换档解释是边翻译边执行。模拟和仿真的区别更微妙模拟是“软件包一层”不要求宿主机硬件支持目标指令仿真则要求宿主机的微程序能直接解释目标指令所以仿真通常出现在硬件验证阶段速度比模拟快一个量级。理解这四个词后面看“虚拟机”“兼容机”才不会乱。2.2 并行性分级与 Flynn 分类法先知道机器属于哪一类从数据处理角度并行性从低到高分成四档字串位串、字串位并、字并位串、全并行。从执行程序角度来看又分成五档指令内部并行、指令级并行、线程级并行、任务级并行、作业级并行。这两套体系容易混淆我一般这样记数据处理分级看“一个时钟周期里同时处理多少位、多少字”执行程序分级看“同时执行多少条指令、多少个线程、多少个任务”。Flynn 分类法则是按指令流与数据流的多倍性来切类别指令流数据流实际例子SISD11经典单核 CPUSIMD1多GPU、Intel AVX 向量指令MISD多1几乎没有商业实现MIMD多多多核服务器、分布式集群课后答案 1.3 只要求分类但实际工作中这个分类特别有用。比如你看到某个算法说“适合 SIMD”本质上是它的数据并行度高、控制逻辑简单可以一股脑喂给向量计算单元而一个多线程服务是 MIMD因为每个核心跑不同的控制流。2.3 CPI 与 MIPS用一段 Python 复现课后 1.6 的计算课后 1.6 给了典型的指令分布要求算有效 CPI、MIPS 和程序执行时间。这种题就是送分题但单位容易写错。我用 Python 复现一遍# 习题 1.6 数据指令执行数量和平均 CPI inst_count [45000, 75000, 8000, 1500] inst_cpi [1, 2, 4, 2] total sum(inst_count) # 总指令数 129500 total_cycles sum(c * n for c, n in zip(inst_cpi, inst_count)) cpi total_cycles / total # 有效 CPI 1.776 freq_hz 400e6 # 主频 400MHz mips (freq_hz / 1e6) / cpi # MIPS 225.225 exec_time_s total_cycles / freq_hz # 单位秒 print(f有效 CPI {cpi:.3f}) print(fMIPS {mips:.3f}) print(f程序执行时间 {exec_time_s * 1e6:.3f} us)代码里total_cycles是所有指令的时钟周期总和exec_time_s用周期总数除以主频得到秒再换算成微秒。输出结果有效 CPI 1.776MIPS 225.225程序执行时间 575 us。注意原答案第 1.6 题写的是“575s”这里明显缺了单位换算正确结果是 575 微秒。这种小坑在课后答案里很常见所以不能只背结论要亲手验算一遍。MIPS 的定义是每秒执行多少百万条指令因此计算时要除以1e6而不是直接用 MHz 数值去除 CPI。3. Amdahl 定律的实战推演从课后 1.7 到 1.8 学会做性能预算3.1 Amdahl 定律的两种写法与参数含义Amdahl 定律是系统结构里最容易被误解的公式。课后 1.7 说的是某一功能处理速度加快 10 倍该功能处理时间占总运行时间 40%求系统整体加速比。公式的标准形式是加速比 Sn 1 / ( (1 - Fe) Fe / Se )其中Fe是可改进部分占总执行时间的比例Se是这部分改进后的加速比。这里的关键是Fe是时间比例不是代码行数比例。很多人把“优化了 40% 的代码”当成Fe0.4这没问题但如果你优化的是所有代码里的一条热点路径Fe应该用 profiler 测出来的时间占比而不是代码行数占比。把 1.7 的数据代入Fe0.4Se10加速比 1 / (0.6 0.4/10) 1.5625也就是性能提升为原来的 1.5625 倍。3.2 多部件优化如何用扩展公式解 1.8(1)1.8 题升级了三个部件可改进加速比分别为 30、20、10已知前两个可改进比例都是 30%问第三个可改进比例达到多少时系统加速比才能到 10。这个场景在真实性能优化里太常见了你手里有几个优化手段每个手段的收益和成本不同要反推某个手段的成本是否值得做。扩展 Amdahl 公式是Sn 1 / ( (1 - ΣFi) Σ(Fi / Si) )其中ΣFi是所有可改进部分的时间占比总和Σ(Fi/Si)是改进后剩下时间占比之和。我把求解代码写出来用符号计算避免手算解方程出错from sympy import symbols, Eq, solve F3 symbols(F3) S1, S2, S3 30, 20, 10 F1, F2 0.3, 0.3 Sn_target 10 # 扩展 Amdahl分母 不可改进部分 各部件改进后时间占比 denominator (1 - F1 - F2 - F3) (F1 / S1 F2 / S2 F3 / S3) eq Eq(1 / denominator, Sn_target) print(solve(eq, F3))运行结果F3 0.36也就是部件 3 的可改进比例要达到 36%。这里1 - F1 - F2 - F3是三个部件之外不可改进部分的时间占比改进前后都不变代价是它把分母压住限制整体加速比上限。3.3 不可改进部分占比1.8(2) 的手算技巧1.8 第二问换了条件三个部件可改进比例分别为 30%、30%、20%加速比分别为 30、20、10问改进后不可加速部分的执行时间在总执行时间中占多少。设改进前总执行时间为T三个部件改进前总占用0.8T不可改进部分是0.2T。改进后三个部件的执行时间变成部件改进前占比改进后执行时间部件 10.3T0.3T / 30 0.01T部件 20.3T0.3T / 20 0.015T部件 30.2T0.2T / 10 0.02T不可加速部分0.2T0.2T所以总时间Tn 0.01T 0.015T 0.02T 0.2T 0.245T不可加速部分占比0.2 / 0.245 0.8163。这个结果很反直觉三个部件都优化了但最终系统里 81.63% 的时间花在了“没优化”的部分。这正是 Amdahl 定律给性能工程师的警告如果热点只分散在多个小模块里逐个优化每个模块的收益会迅速衰减这时候应该考虑能不能合并优化路径而不是继续抠单个部件。4. 指令集与流水线把课后题变成 CPU 设计的决策练习4.1 用 CPU 性能公式拆解 CISC 和 RISC 的取舍课后 2.11 提到 CPU 性能公式CPU 时间 IC × CPI × T。这个公式是评价指令集结构的万能标尺。CISC 的目标代码紧凑IC 小但指令复杂导致 CPI 大、周期时间长RISC 指令简单CPI 小但同样功能需要更多指令IC 大。我列一个假设的对比表指标CISCRISC指令条数 IC100120平均 CPI42时钟周期 T1ns1nsCPU 时间400ns240ns这个例子说明 RISC 即使多执行 20% 的指令只要 CPI 降一半整体仍然更优。这也是为什么现代芯片普遍采用类 RISC 思想即使 x86 也要在内部翻译成微操作来降低等效 CPI。4.2 流水线冲突与定向手动推演 3.16 的三代方案课后 3.16 是流水线章节的分水岭它给了一个 MIPS 循环要求分析不同优化策略下的执行周期。原代码是LOOP: LW R1, 0(R2) DADDIU R1, R1, #1 SW R1, 0(R2) DADDIU R2, R2, #4 DSUB R4, R3, R2 BNEZ R4, LOOP第一种情况没有任何定向硬件分支用排空策略。每个 LW 之后要等 R1 写回DADDIU 要用 R1产生写后读冲突SW 又依赖 DADDIU 的加一结果。没有定向时相关指令之间必须插入 stall每个分支还要清空三段。答案给出每次迭代占 17 个周期99 次迭代加尾部共 1684 个周期。这个数字不需要背但你要能画出来问题出在每条指令都要等前一条写寄存器完成。第二种情况有正常定向路径分支预测失败。定向把 ALU 结果直接送到后续指令的输入端写后读冲突不再需要停顿。但分支仍在第三周期才能解析所以循环的每次迭代缩短到 10 周期总周期 991。第三种情况有定向路径 单周期延迟分支这时可以重排指令。答案给了一个经典调度LOOP: LW R1, 0(R2) DADDIU R2, R2, #4 DADDIU R1, R1, #1 DSUB R4, R3, R2 BNEZ R4, LOOP SW R1, -4(R2)为什么这么排把DADDIU R2提前让R2的更新早于DSUB消除 DSUB 对 R2 的写后读相关SW挪到分支指令的延迟槽里这是利用延迟分支的“无论是否跳转都执行延迟槽指令”语义。因为SW原本在DADDIU R1之后现在R1已经加一所以存储地址改为-4(R2)而R2已经加 4正好指向原地址。每次迭代从 17 周期压到 6 周期总周期 598。这个调度在真实编译器里叫做“指令调度”instruction scheduling和“延迟槽填充”delay slot filling。现在流水线越做越深延迟槽机制已经被动态调度取代但手工调度的思路仍然是理解 Tomasulo 算法和乱序执行的基础。4.3 静态多功能流水线调度从 3.14 看功能部件复用3.14 是一道静态多功能流水线题五个功能段加法用 1、3、4、5 段乘法用 1、2、5 段第 3 段时间 2Δt其余都是 Δt。要算(A1B1)×(A2B2)...这类表达式关键不在套公式而在选择计算顺序。最优顺序是先把四个加法算完再做两个乘法最后加总。为什么因为如果先算乘法乘法的第 2 段会占用加法要用的第 3 段静态流水线同一时间只能连接成一种功能切换功能要等排空。把所有加法集中做完流水线只切换一次。这个思想对应现在处理器里的“功能部件共享”和“功耗墙”优化硬件功能单元越少越要考虑任务发射顺序否则流水线频繁排空吞吐率掉得厉害。5. 用表格和脚本自检把课后答案变成可复用的验证工具5.1 建立概念-计算-场景映射表这份课后答案的目录是按章节走的但复习时最好按“概念 → 公式 → 应用”重新组织。我做了个映射表每看完一章就往里填对应题号和真实场景概念 / 公式课后题号真实应用场景层次结构 / 虚拟机1.1语言运行时设计、JVM 分层CPI / MIPS1.6Benchmark 分析、性能报告解读Amdahl 定律1.7 / 1.8容量规划、优化 ROI 评估CISC vs RISC2.6 / 2.7 / 2.11CPU 架构选型、编译器后端设计流水线冲突与定向3.4 / 3.16处理器级流水线设计、编译器调度向量链接 / 半性能向量长度3.18 / 3.19向量处理器/GPU 性能预测这张表最大的作用不是背诵而是帮你把每个公式和它背后的决策场景绑在一起。比如你在优化一个数据库服务看到查询延迟高第一反应是看Fe是哪个模块的耗时占比再套 Amdahl 定律判断优化上限而不是盲目加机器。5.2 把常用公式封装成 Python 函数我建议把这份答案里的高频公式写成一个arch_utils.py脚本方便以后做题或估算def amdahl(fe, se): 单部件 Amdahl 定律fe 可改进时间占比se 部件加速比 return 1 / ((1 - fe) fe / se) def multi_amdahl(parts): 多部件扩展 Amdahlparts 是 (fe, se) 列表 fe_sum sum(fe for fe, se in parts) improved_sum sum(fe / se for fe, se in parts) return 1 / (1 - fe_sum improved_sum) def effective_cpi(counts, cpis): 有效 CPIcounts 是指令数量列表cpis 是每类指令平均周期数 return sum(c * n for c, n in zip(cpis, counts)) / sum(counts) def exec_time(total_cycles, freq_hz): 总周期数转执行时间秒 return total_cycles / freq_hz # 用课后题做断言自检 assert abs(amdahl(0.4, 10) - 1.5625) 1e-6 assert abs(effective_cpi([45000, 75000, 8000, 1500], [1, 2, 4, 2]) - 1.776) 1e-3 assert abs(multi_amdahl([(0.3, 30), (0.3, 20), (0.36, 10)]) - 10) 1e-6 print(all assertions passed)这几个函数把课后题的核心计算逻辑抽出来了。以后遇到新的性能数据不用再翻公式直接调用。assert的作用是防止你抄错答案后还不自知我用 1.7、1.6、1.8 的已知结论做了三个断言能跑通就说明函数实现和手算一致。5.3 复习时的快速定位法如果你是为了备考建议用“错题三连问”来定位薄弱点第一步这题错了是公式不熟还是概念不清第二步把错题对应的章节名写下来比如“流水线冲突”或“向量处理”第三步用上文那个映射表倒着找同类题连续做三道同类题。这样比从头到尾刷一遍答案效率高很多。另外我习惯用 Git 记录复习进度比如每次更新这个arch_utils.py都提交一次并把错题原因写在 commit message 里回滚复习时直接看错误历史。本文还有配套的精品资源点击获取
