计算机组成原理课后习题精析:CPU性能与数据表示机制

发布时间:2026/10/24 22:10:58
计算机组成原理课后习题精析:CPU性能与数据表示机制

简介:计算机组成与系统结构(袁春风·第三版)课后习题答案汇总,面向计算机专业本科生、考研复习者及自学计组、指令系统与汇编语言的人。内容紧密围绕性能评估、MIPS、CPI、时钟频率、性价比分析等高频考点,通过典型习题给出逐步推导与参考答案;文档用多组机器对比、指令条数与执行时间表格,演示了机器选型、平均时钟周期数、执行时间占比等题型的规范解法,并对易混淆的结论(如速度提升百分比和CPI比例)做了辨析。资源为一篇doc文档,压缩包共1个文件、约1.75MB,按章节整理,收录第一、二章等关键习题并给出答案要点,便于配合教材边学边对照、查漏补缺。已有1190人学习下载,既适合课后消化例题,也适合考前快速梳理重点计算题型。整体是一份紧凑、可复用的计组习题答案解析,能帮助读者在较短时间内掌握性能计算类题目的通用思路。

1. 计组课后习题不是背公式:为什么指令条数少的程序反而跑得慢

计组这门课最容易让人栽跟头的地方,不是公式记不住,而是做题全靠直觉。第三章有一道题很反直觉:同一台机器上,指令序列 S2 只有 8 条指令,另一个序列 S1 有 10 条,结果 S1 跑完只要 38ns,S2 却要花 52ns。指令少的反而慢。这个现象背后,正好是计算机组成与系统结构里性能分析的灵魂:程序执行时间等于指令条数、CPI、时钟周期三者的乘积。三个变量互相牵制,只看其中一个,结论就必然翻车。这份课后习题答案汇总的价值也在这里,它把基准测试、数据表示、运算机制、校验原理全部落成了可验证的数字,适合一题一题推演,而不是把参考答案背下来。搞懂这些题,等于把指令系统底层的设计逻辑重新走了一遍。

2. 性能标尺:CPU时间、MIPS与性价比如何决定机器选型

2.1 CPU执行时间的三因子分解

任何程序的 CPU 执行时间都可以写成:CPU 时间 = 指令条数 × CPI × 时钟周期。这里 CPI 是每条指令的平均时钟周期数,时钟周期是主频的倒数。三个因子中任何一个变化,执行时间都会跟着变。

回到习题 9。编译器把同一段高级语言代码生成两个指令序列 S1 和 S2,机器主频都是 500MHz。S1 的 10 条指令里,5 条 CPI=1、2 条 CPI=2、2 条 CPI=3、1 条 CPI=4,算下来 CPI 是 1.9;S2 有 8 条指令,但 5 条是 CPI=4 的 D 类指令,CPI 高达 3.25。各自执行时间:

  • S1:10 × 1.9 / 500MHz = 38ns
  • S2:8 × 3.25 / 500MHz = 52ns

S2 指令少了 20%,但 CPI 涨了 71%,最终执行时间反而长了 36.8%。习题答案后面补了一句很关键的话:并不是指令条数少的目标程序执行时间就一定少。这句话值得刻在脑子里。

这个公式最大的用途,是把“快慢”这种模糊感受拆成可定位的变量。机器慢,可以先看主频是不是低,再看程序是不是用了大量高 CPI 的指令,最后才看指令条数。很多人调性能一上来就数指令,方向就错了。

2.2 MIPS:峰值与实际的差距

MIPS 是每秒百万条指令,计算公式是指令条数除以执行时间再除以 10^6。习题 5 给了两台机器跑两个基准程序的数据。

看 M1 和 M2 跑 P2 的结果:M1 是 300×10^3 条指令用 3ms,算出来 100MIPS;M2 是 420×10^3 条指令用 6ms,算出来 70MIPS。从 MIPS 看,M1 比 M2 快 1.43 倍。但注意,M1 和 M2 的价格分别是 5000 元和 8000 元,如果只比 MIPS 就草率选 M1,后面性价比那步就会算错。

更迷惑的是峰值 MIPS。习题 6 里,M1 主频 1GHz,M2 主频 1.5GHz,但 M2 的各类指令 CPI 都更高。M1 找一段全是 A 类指令的程序,CPI=1,峰值 1000MIPS;M2 即使用最快的 A、B 两类指令,因为 B 类 CPI=2,峰值也只有 750MIPS。主频更高的机器峰值 MIPS 反而更低,因为峰值 MIPS 只反映“这台机器理论上最快能跑多快”,而现实中程序必然混合使用各种指令,真实 CPI 一定比最理想情况高。

2.3 性价比:算术平均与几何平均谁说了算

习题 5 的第四问和第五问是整套答案里设计得很妙的题目。用户大量使用 P1,关心响应时间,性价比公式定义为 R = 1 /(执行时间 × 价格)。M1 跑 P1 要 10ms,M2 只要 5ms,但 M1 便宜 3000 元。算“执行时间 × 价格”:M1 是 10 × 5000 = 50000,M2 是 5 × 8000 = 40000。值越小性价比越高,所以选 M2。

第五问变成两个程序同等使用,结果就复杂了。用算术平均,还是 M2 胜出;但用几何平均,sqrt(10 × 0.003) × 5000 小于 sqrt(5 × 0.006) × 8000,结论反过来了,应该选 M1。同一批数据,两种平均方法给出完全相反的选型建议。算术平均把执行时间大的 P1 的差异直接放大,几何平均则削弱了量纲差异带来的影响。实际做性能选型时,选择哪种平均方式取决于负载特征——如果耗时集中在单个程序,算术平均更符合直觉;如果是大量同质任务的综合吞吐,几何平均更能反映真实的感知比。

3. 数据表示:补码、移码与IEEE 754的底层逻辑

3.1 补码:为什么减法能变成加法

习题 4 列了一组 8 位定点小数,要求写原码和补码。比如 -0.1001 的原码是 1.1001000,补码是 1.0111000。补码的定义是负数取反加一,但这样定义的原因是为了让减法统一成加法。CPU 里的加法器只要实现加法,减法通过“补码加一”转换为加法,硬件就能少做一套减法电路。

更有意思的是边界值。同样的 8 位格式,+1.0 在原码和补码里都溢出,因为 7 位数值位最大只能表示 0.1111111(不到 1);但 -1.0 的补码 1.0000000 却能表示。这是补码特有的不对称性:定点小数的补码表示范围是 -1 到 +(1-2^-7),负数端比正数端多一个点。习题 6 里给了 [x] 补 = 10000000,求 x,答案是 -128,也是这个道理。补码把“0”唯一化了,原码里 +0 和 -0 都在,补码里 10000000 不再表示 -0,而是整个范围最左边的那个负数。

3.2 移码与偏置:为什么指数要加127

习题 5 让写补码和移码。+1001 的补码是 00001001,移码是 10001001;-1001 的补码是 11110111,移码是 01110111。对比就能看出来,移码和补码的区别只在符号位:补码的符号位取反就是移码。

移码的核心设计动机是让阶码可以按无符号数比较大小。浮点数做加减法时,要先对阶,比较两个指数的大小。如果指数用补码表示,比较大的负数在二进制序里会排在后面,比较电路就要额外处理符号。用了偏置常数后,指数加上 127(或题目里的 16、128),最小指数变成全 0,最大指数变成全 1,大小顺序和二进制序完全一致。IEEE 754 选 127 是因为单精度阶码 8 位,能表示 -126 到 127(全 0 和全 1 另有用途),加上 127 后映射到 1 到 254,正好避开两个保留的边界值。

3.3 IEEE 754的隐藏位与渐进下溢

习题 7 是这份答案里最值得反复看的一道题。R1 内容 0000108BH,R2 内容 8080108BH,三条指令把它们当作无符号数、带符号整数、单精度浮点数,真值完全不同。作为浮点数,R1 的阶码全 0,是非规格化数,没有隐藏的 1,尾数是 0.002116H,指数固定为 -126,真值是 +0.002116H × 2^-126;R2 的阶码是 00000001,规格化数,尾数里有隐藏的 1,指数的真值是 1 - 127 = -126,结果是 -1.002116H × 2^-126。同一个位模式,符号位和阶码差一个 1,规格化状态就完全不同。

规格化数隐藏位的设计很精妙:规格化数尾数最高位必然是 1,那就干脆不存,省下的那一位多存一个有效位。但代价是 0 无法用规格化表示,所以阶码全 0 时改走非规格化路径。习题 15 的范围表里,最小非规格化数是 2^-23 × 2^-126 = 2^-149,而最小规格化数是 1.0 × 2^-126,两者之间是连续的、平滑逼近 0 的,而不是直接掉到 0。这种“渐进下溢”避免了数值从 2^-126 突然跳成 0 的精度断层。

3.4 大端小端:同一个数在不同内存布局下的机器数

习题 17 让画出 x=-0.125、y=7.5、i=100 在大小端机器上的内存分布。先算出机器数:-0.125 是 BE000000H,7.5 是 40F00000H,100 是 0064H。大端机把最高字节放在最低地址,地址 100 处是 BEH;小端机把最低字节放在最低地址,地址 100 处是 00H。

这题的陷阱在于,很多人以为“大小端只是字节序颠倒”,实际上浮点数的符号位、阶码、尾数在内存里也跟着一起按字节倒置。x 在地址 100~103,在小端机上就是 00H 00H 00H BEH,连符号位所在的字节都被搬到了最后。网络协议用大端,x86 用端小端,跨端传数据时如果直接读内存字节,符号位、指数位全部错位,这就是为什么序列化要用明确的字节序约定。

4. 运算机制:类型转换、移位、乘除优化与检错纠错

4.1 unsigned的len-1:一个循环灾难的根因

习题 9 的 C 程序,参数 len 是 unsigned 类型,循环条件是 i <= len-1。len 为 0 时,程序没有返回 0,而是触发存储器访问异常。

float sum_elements(float a[], unsigned len) { int i; float result = 0; for (i = 0; i <= len-1; i++) result += a[i]; return result; }

根因是 unsigned 的溢出行为。len=0 时,len-1 不是 -1,而是 0xFFFFFFFF,即 unsigned 能表示的最大值。任何有符号的 i 和它比较时,i 先被隐式转换成 unsigned,于是 i 永远小于等于 0xFFFFFFFF,循环体一直执行,a[i] 越界访问。修复方法有两个:把 len 声明成 int,或者把测试条件改成 i < len。后者更符合直觉,因为 i < len 在 len=0 时直接不进入循环。

这类问题在真实代码里很常见,尤其是把接口里的长度参数写成 unsigned 又做了 -1 运算的。C 语言的隐式类型转换规则决定了,有符号和无符号混合比较时,有符号会转成无符号,负数的真值瞬间变成几十亿。

4.2 逻辑移位与算术移位:func1和func2为什么结果不同

习题 3 的两个函数看起来只差一个括号,行为却有本质区别。

int func1(unsigned word) { return (int) ((word << 24) >> 24); } int func2(unsigned word) { return ((int) word << 24) >> 24; }

func1 全程按无符号数处理,左移 24 位再逻辑右移 24 位,相当于把高 24 位清零,结果必然是非负的。func2 先把 word 转成 int,左移 24 位后做的是算术右移,右移补的是符号位。所以 w=0000 0080H 时,func1 返回 +128,func2 先左移得到 80000000H,符号位是 1,算术右移后得到 FFFFFF80H,对应 -128。而 w=0000 0100H 时,左移 24 位把唯一的 1 移出了 32 位,结果是 0。

这里值得停下来想一层:为什么带符号整数必须用算术右移?因为补码的负数右移时,高位补 0 会让负数变成正数,数值完全错乱。算术右移保持符号位,相当于对负数做向下取整的除以 2 的幂。代价是右移对负数不是向零取整,这正好引出下一节的除法优化。

4.3 编译器优化:乘法如何变成移位与加减

习题 5 给了一段反编译得到的机器代码,要从它反推出原函数里的常量 M 和 N。

int optarith(int x, int y) { int t = x; x <<= 4; x -= t; if (y < 0) y += 3; y >> 2; return x + y; }

x<<=4 是乘 16,再减掉原来的 x,就是 x×15,所以 M=15。y>>2 是除以 4,但算术右移对负数向负无穷取整,而 C 语言整数除法向零取整,所以先给负数加上一个偏置量。y<0 时 y+=3,加上 3 之后右移 2 位,等价于向零取整的除以 4。为什么要加 3?因为 2^2 减 1。对任意负数 y,右移 2 位是 floor(y/4),要向零取整,就得先把负数垫高 3,使得 floor((y+3)/4) = trunc(y/4)。如果换成偏置 2,y=-3 时 -3+2=-1,右移两位还是 -1,但 -3/4 向零取整是 0,就错了。

这道题同时也是指令系统的缩影:高级语言里的乘除,编译成一堆移位、加减、条件分支,而不是真的调用乘法指令。机器不提供乘除指令,程序照样能跑,只是生成的指令序列更长,执行更慢。

4.4 传输可靠性:奇偶校验、SEC与CRC怎么工作

数据在总线上传输时会出错,习题 18、19、20 分别展示了三个层次的检错纠错机制。

奇偶校验最简单。32 位数据 8F 3C AB 96H 分成四个字节,每个字节配一个奇校验位,要求每 9 位里 1 的个数为奇数。8F 展开是 1000 1111,有 5 个 1,校验位 0;3C 是 0011 1100,有 4 个 1,校验位 1;AB 是 1010 1011,有 5 个 1,校验位 0;96 是 1001 0110,有 4 个 1,校验位 1。接收端收到的数据变成 87 3C AB 96H,校验位是 0101。重新计算前四个字节的奇校验,87 只有 4 个 1,期望校验位是 1,收到的是 0,异或结果为 1,定位到第一个字节出错。

SEC 海明码更进一步,能指出具体哪一位错。16 位数据的 SEC 码用 5 个校验位,放在 1、2、4、8、16 位,其余位置放数据。每个校验位覆盖一组特定位置的数据位,出错时重新计算校验位,和发送方的校验位异或得到故障字,故障字的二进制值正好是出错位的编号。习题 19 里数据错在第 5 位 M5,故障字是 01001,即 9,对应码字第 9 位——正好是 M5 的位置。这个设计的巧妙之处在于校验位位置选 2 的幂,让每个出错位都产生一个唯一的故障字。

CRC 校验则是多项式的世界。习题 20 的数据是 100011,生成多项式 G(x)=x^3+1,即二进制 1001。发送方在数据后补 3 个 0,用模 2 除法去除以 1001,余数是 111,CRC 码就是 100011 111。接收方收到 100010 111,同样做模 2 除法,余数 001,非 0,判定出错。这里的除法不是算术除法,是按位异或,相当于判断接收到的多项式是否能被生成多项式整除。不能整除就说明有错,这正是 CRC 能被广泛用于链路层的原因。

5. 常见问题排查:计组题里最常翻车的五个现场

5.1 现象:CPI 直接相比,差多少就算快多少

习题 6 后面有个思考题:程序 P 在 M1 上 CPI 是 2.4,M2 上 CPI 是 3.8,有人说 M1 比 M2 快 (3.8-2.4)/3.8 = 36.8%。

原因:CPI 只是执行时间的一个因子,这台题目里 M1 主频 1GHz、M2 主频 1.5GHz。完整的执行时间是 CPI × 指令条数 / 主频。假设指令条数为 N,M1 是 2.4N ns,M2 是 3.8N/1.5 = 2.53N ns,M1 只快约 5%。CPI 直接比,错在丢掉了时钟周期这一个维度。解决:永远把执行时间公式三项写齐再算,任何性能结论都要落到时间上。

5.2 现象:指令条数少,程序一定更快

习题 9 的 S1 有 10 条指令,S2 只有 8 条,S2 反而慢 14ns。原因:S2 的 CPI 是 3.25,是 S1 的 1.7 倍,多出的 D 类指令全是高 CPI 指令。解决:比较两个程序或两个编译版本,要看 “指令数、CPI、时钟周期” 的乘积,任何单一指标都不能直接证明快慢。

5.3 现象:十进制小数转二进制,算几位就停

习题 10 里,+1.7 转成二进制是无限循环小数。如果手算时取 0.1101 就收手,后续的阶码、尾数全是错的。参考答案用 0 舍 1 入,得到 0.011011×4^1,阶码是 17,尾数 0.011011。-0.12 转出来是 -0.000111101B,要左移到 0.011111×4^-1 再取补码。原因:浮点格式尾数位有限,十进制小数在二进制里可能无限循环,必须按格式位数做舍入。解决:先确定格式有多少位尾数,再决定保留几位,最后一步做 0 舍 1 入,不要提前截断。

5.4 现象:unsigned 的边界值当普通整数算

习题 9 的 len=0 时,len-1 是 0xFFFFFFFF 而不是 -1,循环不退出。习题 8 里 -1 < 0U 的结果是 0,因为 -1 转成 unsigned 后是 4294967295。原因:C 语言的隐式类型转换把有符号转成无符号,负数变成大正数。解决:涉及 unsigned 的循环条件不用 len-1 这种写法,直接 i < len;混合比较时,先想清楚两边会被转换成什么类型。

5.5 现象:除法优化偏置量想当然

习题 5 的 y/4 优化,有人觉得负数加 2 就行。加 2 对 y=-4 恰好成立,但对 y=-3 就错了:-3+2=-1,算术右移两位是 -1,而 -3/4 向零取整是 0。原因:偏置量必须是 2^k - 1,目的是让 floor((y+bias)/2^k) 等于 trunc(y/2^k)。解决:不要背特例,直接从向下取整和向零取整的关系推,k 位右移配 2^k - 1 的偏置。

6. 设计取舍:每个聪明的设计都在“浪费”位宽

这套习题答案看下来,会发现一个规律:计算机里每个关键设计都在用某种“浪费”换另一种收益。补码浪费了直观性,换来的是加减法统一,CPU 里少一套减法器;移码浪费了纯二进制的大小顺序直觉,换来浮点指数可以直接按无符号数比较;IEEE 754 浪费了规格化数尾数最高位的一位空间,但把这一位变成隐藏位,多给了 24 位有效精度;非规格化数浪费了指数编码空间,换来的是下溢时数值能平滑逼近 0,而不是从 2^-126 直接跳成 0。

这些取舍在习题里体现得非常具体。习题 4 里 +1.0 溢出而 -1.0 不溢出,就是补码不对称性的代价;习题 15 里最大规格化数和最小非规格化数相差那么多数量级,就是浮点表示把范围撑大后精度分布不均的代价。做题的时候如果不看设计动机,这些边界情况都像冷门考点,但一旦理解了“每个位模式都有它的用途和代价”,它们就成了验证设计合理性的检查点。

从那以后,我每次拿到一道计组题,都强制自己先列出参与计算的公式和前提,再代入数值,最后才看结论。尤其是性能题和浮点题,直觉几乎总是错的,公式和位模式才是唯一可靠的工具。这份习题答案汇总适合放在手边,边做边对照,把每一道题的“为什么”推演一遍,比直接背参考答案有用得多。希望帮到你。

本文还有配套的精品资源,点击获取