简介:计算机体系结构是计算机专业的核心课程,课后习题常涉及概念辨析、设计与计算,不少初学者苦于缺少可靠的答案参考。这份按章节整理的习题答案文档从第1章系统结构基本概念延伸到第9章机群,覆盖指令集结构分类、流水线技术、指令级并行、存储层次、输入输出系统、互连网络、多处理机等核心专题;其中既有透明性、Amdahl定律、CPI等关键术语解释,也有流水线计算、多处理机设计等综合性问题,能帮助读者巩固知识点、核对解题思路。压缩包内为1个doc文件,大小1.25MB,内容集中,便于按顺序阅读或定位章节。截至目前已有296人学习浏览,适合期末复习、考研备考或自学对照使用,是一份实用的计算机体系结构配套学习材料。
1. 从“张”字入手:计算机体系结构课后题到底在问什么
在搜索引擎里输入“计算机体系结构课后习题答案张”,多半是中文本科课堂里拿着张晨曦主编的教材,或者一份标注“张老师”的课后作业。与其背答案,不如把这些题当成常数分析题:题干里的指令序列、Cache 参数、并行比例,最后都能转换成流水线 CPI、平均访存时间和加速比三个可验证的数字。这篇内容围绕张版教材最常见的三种出题路径展开,给出可复现的 Python 脚本和模拟器对照思路;无论你在湖南大学、国科大,还是自学胡伟武那本《计算机体系结构教学与习题指导(第 2 版)》,都能按同一个框架自检。
2. 流水线型课后题:CPI、停顿周期与冒险表的统一解法
2.1 用“一拍对应一个阶段”画时间轴,替代死记公式
张版教材对流水线的考察几乎都从五段经典流水线切入:取指 IF、译码 ID、执行 EX、访存 MEM、写回 WB。大多数习题并不会真的让你画完整时序图,而是问“总共需要多少个时钟周期”,或者“某条指令序列带多少停顿”。这时最稳妥的起点是先把每条指令在每个周期占用的阶段列出来。规则只有一条:后一条指令只能在硬件资源空闲时进入下一级,且同一个周期内同一个资源不能被两条指令同时占用。
例如执行五条没有任何冒险的指令,最后一条指令完成于5 + (5 - 1) = 9个周期。画成时间轴,前五个周期是流水线填充,后五个周期是尾段排空,重叠部分被抵消了一次。常见错误是直接算成5 + 5 = 10,把填充段重复计数。考试里只要出现这类基础题,第一步就写“流水线启动时间为 m - 1 个周期”,后面无论怎么加停顿都不会垮。
2.2 用表格记录停顿,把 load-use 和分支惩罚一次性列清楚
两道容易混的题型是数据冒险与控制冒险。数据冒险里最经典的是LW X1, 0(X2)紧接ADD X3, X1, X4,ADD 在 ID 阶段就需要 X1,但 X1 要到 MEM 阶段结束后才写回,所以必须暂停一拍。控制冒险则要看分支结果何时产生;如果按习题常用的“分支在 MEM 阶段才算出跳转目标”口径,分支目标指令最早只能从分支后第 2 个周期开始取指。
下面这张表把两类停顿放在同一个算例里。规定分支未跳转,保持顺序执行:
| 指令 | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | C10 | C11 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| I1: LW X1 | IF | ID | EX | MEM | WB | ||||||
| I2: ADD | IF | ID | STALL | EX | MEM | WB | |||||
| I3: OR | IF | ID | EX | MEM | WB | ||||||
| I4: BEQ | IF | ID | EX | MEM | WB | ||||||
| I5: SUB | IF | ID | EX |
从表里可以直接读出总周期数是 11,不是无停顿状态下的 9。多出来的两拍中,C4 是 load-use 停顿,C7 到 C8 是分支结果未落地造成的控制冒险惩罚。把表格画完再套公式,一般不会漏掉任何一拍。
2.3 用最小 Python 脚本验证周期数,覆盖批量练习
手工画表适合单题,但做一整章作业时,用短脚本先算一遍更省时间。下面的函数把“无停顿的基准周期数”和“每个停顿点额外增加的周期数”分开加总:
def pipeline_cycles(n_inst, stalls=[]): # n_inst: 指令条数 # stalls: 每条指令插入前需要停顿的周期数 total = n_inst + 4 # 五级流水线基准周期 for s in stalls: total += s return total cases = [ {"n": 5, "stalls": [1, 0, 1]}, # 与 2.2 表格一致 {"n": 10, "stalls": [0] * 10}, # 无停顿 ] for c in cases: print(c, pipeline_cycles(c["n"], c["stalls"]))n_inst是题干给出的指令总数,4是五级流水线的启动损失。stalls列表里的每个元素对应一条指令执行前的额外等待周期,load-use 停顿记 1,分支预测错误每多损失一个周期就多记一个 1。第二组输出是 14,如果你手算得到 10,多半是漏掉了流水线填充段;用这个脚本可以快速反向定位错误位置。
3. Cache 型课后题:命中率、地址切分与缺失代价如何一次算对
3.1 先判断映射方式,再决定地址字段怎么拆
Cache 题一般给出四个参数:地址位宽、块大小、Cache 容量、相联度。解题起点是把地址拆成Tag | Index | Offset三段,但拆法必须跟着映射方式走。直接映射下 Cache 被分成固定个块,每块对应唯一的 Index;全相联下所有块共享一个组,Index 位数为 0;组相联下组数等于容量除以块大小乘相联度。
| 映射方式 | 地址组成 | 需要从题干提取 |
|---|---|---|
| 直接映射 | Tag + Index + Offset | 总块数、Cache 容量 |
| 全相联 | Tag + Offset | 块大小、总容量 |
| 组相联 | Tag + Index + Offset | 容量、块大小、相联度 |
拿到题先做判断题:题干有没有写“组数”“路数”“fully associative”这些词。没有这些词的直接映射题最容易算错,因为很多学生把直接映射当成组相联,给 Index 多留了几位,导致 Tag 变短,最后算出的命中率对不上。
3.2 手算步骤:先偏移,再索引,最后 Tag
第一步算偏移位OffsetBits = log2(块大小),第二步算索引位IndexBits = log2(组数),第三步用地址总位宽减去前两者得到 Tag 位数。这里最容易漏的是单位换算:Cache 容量是 KB,块大小是 B,必须先统一成字节再相除。
用一个常见例子:32 位地址、64B 块、16KB 数据 Cache、4 路组相联。块内偏移需要log2(64) = 6位;组数16K / (64 * 4) = 64,所以索引位也是 6 位;Tag 位32 - 6 - 6 = 20位。地址0x12345678的取值被切分成:高 20 位是 Tag,中间 6 位是 Index,低 6 位是 Offset。作答时先把这些位数写进解答开头,后面比对命中就不用反复重算。
3.3 用 Python 脚本自动切分地址,并交叉验证命中率
手算一两个地址没问题,但习题会连续给一长串地址,手工切换容易烦躁。下面这段脚本专门做地址字段切分:
def split_cache_addr(addr, block_size_bytes, num_sets): offset_bits = (block_size_bytes - 1).bit_length() index_bits = (num_sets - 1).bit_length() index_mask = (1 << index_bits) - 1 offset = addr & ((1 << offset_bits) - 1) index = (addr >> offset_bits) & index_mask tag = addr >> (offset_bits + index_bits) return tag, index, offset for addr in [0x12345678, 0x1234567c, 0x12346678]: print(split_cache_addr(addr, 64, 64))block_size_bytes是块大小,num_sets是组数。bit_length()用来把数值直接转换成二进制位数,前提是两者都为 2 的幂。index_mask按索引位数生成掩码,右移 Offset 位后做按位与;最后把整体右移得到 Tag。运行结果中前两个地址 Index 相同、Tag 相同,只有 Offset 不同,这正好说明它们在同一块 cache line 内,第二个访问必然命中;第三个地址 Index 变化,对应一次新的替换或缺失。
3.4 平均访存时间公式与常见丢分点
平均访存时间用AMAT = HitLatency + MissRate × MissPenalty。丢分点集中在 MissPenalty 的语义上:有些题目把“下一级存储器的访问时间”称为 Miss Penalty,这时它本身已经包含命中延迟;有些题目把“额外损失周期”称为 Miss Penalty,这时它等于下一级访问时间减去本级命中时间。答题时先把题干里那句话抄成符号,再代入数字,能避免一半错误。
进阶一点,两级 Cache 的题目会把 L1 miss rate、L2 hit time、L2 miss rate 一起给出。此时 AMAT 需要逐级展开,公式变成L1_hit + L1_miss_rate * (L2_hit + L2_miss_rate * Memory_penalty)。这个二级公式在张版课后题里经常以论述题形式出现,不要直接套一级 AMAT,否则少算一段 L2 命中时间。
4. 并行与性能型课后题:Amdahl、MIPS 与可靠性计算陷阱
4.1 Amdahl 定律:把“可并行比例”从题干里精确摘出来
张版教材里最常考的并行题是 Amdahl 定律:原程序总时间 T,其中可并行部分占 P,不可并行部分占 1 - P。使用 n 个处理器时,加速比公式为S = 1 / ((1 - P) + P / n)。学生常犯的错误是直接把代码行数比例或循环执行次数当成 P,实际上 P 必须是时间占比。题干写出“程序运行时间的 40% 可并行”,P 就是 0.4;写出“40% 的代码可并行”,P 也需要按每部分执行时间的权重换算。
如果题目反过来问“加速比达到 S 需要多少处理器”,把公式变形为n = P / (1 / S - (1 - P))。注意分母大于零才可能有解;若计算得到负的数,代表目标加速比已经超出 Amdahl 上限。答案应直接写“不可能通过增加处理器达到”,然后说明原因。
4.2 在性能比较题里,用公式算 MIPS 要注意对比口径
MIPS 的定义是主频 / (CPI × 10^6)。多数习题直接给主频和 CPI,让学生算单机性能。下面这段脚本输入频率和 CPI,输出每秒执行的百万指令数:
def mips(freq_hz, cpi): # freq_hz: CPU 时钟频率,单位 Hz # cpi: 每条指令平均周期数 return freq_hz / (cpi * 1e6) print(mips(2.5e9, 2.0))freq_hz越大,每秒可用的时钟周期越多;cpi越小,同一周期完成的指令越多。结果是 1250,表示该处理器每秒执行 12.5 亿条指令。比 MIPS 更稳妥的做法是直接比执行时间IC × CPI × Cycle_time,因为 MIPS 相同的两台机器可能指令条数不同。遇到两台机器跑不同程序时,参考答案通常会指出:MIPS 只能作为同一体系结构下的小范围参考。
4.3 可靠性计算题:把 MTTF、MTTR 和失效率分开记
可靠性题的目标是把题干里的自然语言翻译成公式。下面这张表列了最常见的对应关系:
| 题干说法 | 对应指标 |
|---|---|
| 平均故障间隔时间 | MTBF |
| 平均修复时间 | MTTR |
| 平均连续正常运行时间 | MTTF |
串联系统失效率是各部件失效率之和,MTTF = 1 / λ_total。并联冗余系统的计算更复杂,但教材习题通常只考两个部件并联的情形:单个组件MTTF0,双模冗余系统的MTTF按概率积分得到3 * MTTF0 / 2。如果记不住结果,就列积分式:系统寿命等于冗余组件中最后一个失效的时间,先求两个失效时间的最大值概率分布再积分。
可靠性题里刻意设置的陷阱是 MTBF 与 MTTF 混用。只要题干提到“可修复”“平均修复时间”,就必须把 MTTR 加回 MTBF。可用性公式A = MTTF / (MTBF),也就是MTTF / (MTTF + MTTR)。这一步写错,后面的并行系统可用性基本全错。
5. HNU、国科大与胡伟武《习题指导》里的计算机体系结构题型变种
5.1 湖南大学计算机体系结构:GPU 与 SIMD 的课堂补充题
湖南大学计算机体系结构课在传统教材之外,通常还会补充 GPU 与 SIMD 计算题。这类题目常见提问方式为“一个 Warp 包含 32 个线程,遇到分支后一半走 A 路径、一半走 B 路径,求 SIMD 利用率”。由于同一时刻单个 SIMD 通道只能执行一种控制流,两条路径只能串行。如果题目按“周期数占比”算,则两个周期中只有一个周期有线程在工作,利用率是 50%;如果按“活跃线程比例”算,只看单个周期内是 16/32,也是 50%。两者结果一致,但答题时要写清楚用哪种口径,否则会被认为概念混淆。
5.2 国科大计算机体系结构:量化分析为主的推导题
国科大计算机体系结构更注重量化分析,习题常给出一组实验数据,让学生判断某种优化是否有效。这类题不用非要把课后题答案背出来,关键是列出可比较的度量值。比如评估缓存优化时,把缺失率变化、平均访存时间变化放在一张对比表里;评估功耗时,把动态功耗和静态功耗分别列出。题目只要问“是否值得做”,就先找到一个约束方程,面积、功耗、延迟三者之间满足给定资源限制的优化才成立。
5.3 胡伟武《计算机体系结构教学与习题指导(第 2 版)》:龙芯背景下的一致性协议题
胡伟武那本《计算机体系结构教学与习题指导(第 2 版)》把大量内容放在龙芯处理器背景下,Cache 一致性协议是高频考点。面对 MESI 状态迁移题,第一步画出状态集合 M、E、S、I,第二步写清本地请求和总线请求两类事件,第三步核对每个迁移方向。下面的 Python 字典可以被当作“转移规则核对表”,在手工答题后检查状态迁移是否合法:
mesi_rules = { ("M", "bus_read"): "S I", ("E", "bus_read"): "S I", ("S", "bus_read"): "S", ("I", "bus_read"): "I", } for (state, event), nexts in mesi_rules.items(): print(state, event, "->", nexts)这个脚本并不实现完整 MESI,它只是把你在答案上写出的规则逐行登记,检查同一(state, event)是否会出现冲突。答 MESI 题最常见的丢分点是 M 状态收到总线读请求后直接变成 F 状态,但 MESI 的常见教学版本里只会变成 S 或 I,不存在 F 状态;F 是 MOESI 里的概念。答题前先确认教材用的是哪种一致性协议,再决定要不要把 F 写进去。
三所院校和两套教材的出题风格可以整理成一张速查表:
| 出题来源 | 常考题型 | 解题记忆关键词 |
|---|---|---|
| HNU | GPU 分支发散 | SIMD 利用率、Warp、活跃线程 |
| 国科大 | 量化性能分析 | AMAT、功耗面积、Pareto |
| 胡伟武《习题指导》第 2 版 | Cache 一致性 | MESI、总线请求、状态迁移 |
6. 最后的验证手段:用 Python 和 Gem5 交叉验证课后题答案
6.1 用 Gem5 快速复现 Cache 缺失率
手算题目的结论可以用模拟器交叉验证。先建立一个最小 Gem5 环境,编译 x86 目标,然后跑configs/example/se.py模式,传参设置 L1 数据 Cache 容量与相联度。命令大致是:
build/X86/gem5.opt configs/example/se.py \ -c ./mem_access_test \ --caches --l1d_size=32kB --l1d_assoc=4mem_access_test这段小程序最好让它按课后题里的地址序列访问同样的 Cache 配置。运行结束后,在m5out/stats.txt里找到这一行:
grep "overall_miss_rate::total" m5out/stats.txt得到的是模拟器实测缺失率,把它和手算缺失率对照。由于模拟器包含真实替换策略和预取行为,结果不会完全相等,但趋势应当一致;如果手算命中率 80%,模拟器给出 40%,那就是地址切分或组数换算出了问题。
6.2 对照时先排除三个系统误差
第一,模拟器默认替换策略可能是 LRU,而手算题有时假设理想替换,二者会有偏差。第二,模拟器会统计指令访存和数据访存的总和,手算题通常只算数据 Cache,统计口径要分开看。第三,硬件预取在 Gem5 的某些配置里默认开启,访问模式一旦有规律,命中率会被抬高。把这三个误差写进验证记录,就能明确区分“思路错误”和“环境差异”。完成这一步后,整份课后题答案才真正从纸面推导变成了可解释的工程结论。
本文还有配套的精品资源,点击获取