有效CPI与MIPS计算:40MHz处理器作业题详解
2026/9/20 8:41:55 网站建设 项目流程

简介:计算机系统结构第一章作业PPT,围绕性能评估这一核心主题,针对有效CPI、MIPS速率及程序执行时间三个基本指标展开讲解,内容取自教材配套的经典练习题。资料以一台40MHz处理机上的标准测试程序为例,统计整数运算、数据传送、浮点运算、控制传送四类指令的数量与时钟周期,推导出有效CPI为2.0、MIPS速率为20MIPS、执行时间为5ms;同时根据指令混合比计算平均CPI为9.46及相应MIPS速率约4.23MIPS,完整呈现两类典型性能计算题的已知条件、公式代入和数值结果,帮助读者从不同指令类型的时钟周期和占比入手,理解处理器效率的评估方法。压缩包仅含1个PPT文件,大小1.29MB,便于直接打开使用。目前已有123人浏览/学习这份作业资料,适合计算机系统结构课程初学者、考研复习者及需要快速理解CPI与MIPS计算的工程人员参考。

1. 计算机系统结构第一份作业:40MHz老机器上的性能公式

计算机系统结构课程的第一章作业,往往不是写代码,而是先算一道看起来很老的题:一台 40MHz 处理机执行一个混合指令测试程序,让你根据给定的指令数和每类指令的时钟周期数,求出有效 CPI、MIPS 速率和执行时间。题目只有三四行数据,但里面一环套一环:总时钟周期怎么汇总、CPI 为什么要做加权平均、MIPS 和主频以及 CPI 是什么关系、单位怎么换算,所有坑都埋在里头。这道题适合正在修计算机系统结构或计算机组成原理的学生,也适合日常要跟 perf 统计、性能估算打交道的工程师——很多人在真实项目里算 IPC、CPI,反而把公式记反了。

2. 指令数与时钟周期:有效 CPI 和它的两种算法

2.1 有效 CPI 的定义:为什么用“时钟周期”而不是“秒”

CPI(Cycle Per Instruction)字面意思是执行一条指令平均花掉几个时钟周期。处理器内部所有操作都以时钟周期作为最小时间刻度:取指、译码、执行、访存、写回,每一步都可能占一个或多个周期。不同指令功能不一样,耗费的周期数也不一样,比如整数运算往往比访存指令快。所谓有效 CPI,就是把程序里所有指令的 CPI 按“指令条数”做加权平均后的值。

严格写出来是两个等价形式。第一种是从定义出发:

有效 CPI = 总时钟周期数 / 总指令数

第二种是把总时钟周期展开成每类指令的贡献之和:

有效 CPI = Σ(第 i 类指令数 × 第 i 类 CPI) / Σ 指令数

这两个公式必须放在一起理解。前者适合你已经知道程序总共跑了多少个周期、多少条指令;后者适合像作业题这样,只给了“指令混合情况”。注意这里不能做简单算术平均,因为各类指令数量不一样。把四条指令等权重平均,结果会完全失真,后面第 4 章会专门把这类错误列出来。

2.2 第一题的数据表:整数运算其实是 1 个周期

第一题原始数据长这样:

指令类型指令数时钟周期数周期总数
整数运算45,000145,000
数据传送32,000264,000
浮点运算15,000230,000
控制传送8,000216,000
合计100,000155,000

表里最容易翻车的地方是第一行:整数运算是 1 个时钟周期,后面三类才是 2。很多流传的答案把四类全部写成 2,算出来的 CPI=2.0,实际应该是 1.55。之所以产生这种误读,是因为题干排版把四类指令名连在一起,数值区又写成一串“1 2 2 2”,第一眼很容易忽略 1 和 2 的区别。

把数据代进去:总指令数 45,000+32,000+15,000+8,000=100,000,总周期数 45,000×1 + 32,000×2 + 15,000×2 + 8,000×2=155,000,因此有效 CPI=155,000 / 100,000=1.55。这比 2.0 低不少,说明该测试程序里六成以上指令是单周期整数运算,整体开销被拉下来了。

这种“先汇总、再除”的算法,用命令行可以一步到位。在 bash 里把每行当作“指令数 周期数”喂给 awk:

awk 'BEGIN {n=0; cyc=0} {n += $1; cyc += $1 * $2} END {printf "总指令数=%d\n总时钟周期=%d\n有效CPI=%.4f\n", n, cyc, cyc/n}' <<EOF 45000 1 32000 2 15000 2 8000 2 EOF

awk 的$1是第一列指令数,$2是第二列周期数,cyc += $1 * $2等价于每类指令的“条数 × 周期数”累加。END 块里的cyc/n就是总周期除以总指令数,得到 1.55。用这种方法的好处是数据改动只改输入行,计算逻辑不用重写,后面第二题换个表格也能直接复用。

2.3 执行时间:把周期数换算成秒

题目给的主频是 40MHz,即每秒 40,000,000 个时钟周期。一个时钟周期的时间就是主频的倒数:

时钟周期时间 = 1 / 40,000,000 秒 = 25 纳秒(ns)

程序执行时间 = 总时钟周期数 × 时钟周期时间。第一题总周期数 155,000,所以执行时间为 155,000 × 25ns = 3,875,000ns,也就是 3.875ms。

这里要习惯一个换算链:1ms=1,000μs=1,000,000ns。很多人在这一步把 25ns 直接乘上 155,000,得到 3,875,000 后忘了除以 1,000,000,写成了 3.875 秒,数量级差了整整 1000 倍。更稳妥的写法是直接套联合公式:

执行时间 = 指令数 × 有效 CPI × 时钟周期时间 = N × CPI / f

代入 N=100,000、CPI=1.55、f=40MHz,得到 100,000 × 1.55 / 40,000,000 = 0.003875 秒。这个式子把主频放在分母上,能直接避免纳秒和毫秒换算时出错的概率。可以顺手对比一下现实处理器:一台 4GHz 的机器如果同样跑 100,000 条指令且 CPI 接近 1,执行时间只有 0.025ms,快了将近 155 倍——这就是主频提升带来的直观收益。

3. 指令混合比下的平均 CPI 与 MIPS 速率

3.1 第二题的关键变化:从“指令数”变成“混合比”

第二题换了种给数据的方式:总指令数 200,000,但不再直接列出每类指令的条数,而是给出 CPI 和指令混合比。原始数据按表格拆开是这样:

指令类型CPI混合比
算术和逻辑160%
高速缓存命中的加载/存储218%
转移412%
高速缓存缺失的存储器访问810%

注意这里 CPI 的取值是“1 2 4 8”,不是“12 4 12 12”。题干把 1、2、4、8 四组数字和四个百分比连排在一起,特别容易断句错误。从系统结构角度看,1、2、4、8 这组数也远比 12、4、12、12 合理:算术逻辑指令单周期,Cache 命中的加载/存储多一次命中访问所以是 2,转移指令可能引起分支开销所以是 4,Cache 缺失的存储器访问要走到主存所以代价最高,8 个周期。

平均 CPI 的计算直接用混合比加权:

平均 CPI = 60%×1 + 18%×2 + 12%×4 + 10%×8 = 0.6 + 0.36 + 0.48 + 0.8 = 2.24

为了验证这个加权结果,可以把它还原成指令条数再算一遍:200,000 条指令按比例拆成 120,000、36,000、24,000、20,000,总周期数为 120,000×1 + 36,000×2 + 24,000×4 + 20,000×8 = 448,000,448,000 / 200,000=2.24。两种算法完全一致,说明“混合比加权”本质上就是“指令数加权”消去了公共分母 N。

3.2 MIPS 速率:40MHz 处理器能跑到多少

MIPS 是每秒执行的百万条指令数。只要知道主频(MHz)和 CPI,就能直接算:

MIPS = 主频(MHz) / 有效 CPI

第一题:40 / 1.55 ≈ 25.8 MIPS。第二题:40 / 2.24 ≈ 17.9 MIPS。两个结果差得挺多,原因是第二题里高 CPI 指令占比更大,其中 10% 的 Cache 缺失访存指令一个就要吃掉 8 个周期。

这里必须强调单位问题。40 / 2.0 得到的是 20 MIPS,意思是每秒 2000 万条指令。有人会写成“20,000,000 MIPS”,这在数值上差了 100 万倍。MIPS 里的“M”已经带了“百万”,不要再乘一次 10^6。检查方式很简单:200,000 条指令按 2.24 的 CPI 跑,执行时间是 200,000 × 2.24 × 25ns = 11.2ms;200,000 条指令除以 11.2ms,每秒约 17,857,143 条,即 17.86 MIPS。三条数据能互相印证,说明 17.9 是对的。

MIPS 的局限也要知道:它跟指令集有关,同样的 20 MIPS,在 RISC 机器上和 CISC 机器上实际完成的工作量可能差很多。所以课程里用 MIPS 做教学指标,真实工程里更常看 IPC(每周期指令数,CPI 的倒数)配合总执行时间一起评估,而不是单独盯一个 MIPS。

3.3 用 Python 把两组计算一次跑完

把前面两题的数据组织成结构化格式,用 Python 重算,顺便把执行时间也补上:

# 第一题:每项是(名称, 指令数, 周期数) case1 = [ ("整数运算", 45000, 1), ("数据传送", 32000, 2), ("浮点运算", 15000, 2), ("控制传送", 8000, 2), ] n1 = sum(c[1] for c in case1) cyc1 = sum(c[1] * c[2] for c in case1) cpi1 = cyc1 / n1 print(f"第一题: N={n1}, 周期数={cyc1}, CPI={cpi1:.2f}") print(f" MIPS={40 / cpi1:.2f}, 执行时间={cyc1 / 40 / 1000:.4f}ms") # 第二题:每项是(名称, 混合比, CPI) case2 = [ ("算术和逻辑", 0.60, 1), ("Cache命中加载/存储", 0.18, 2), ("转移", 0.12, 4), ("Cache缺失访存", 0.10, 8), ] cpi2 = sum(p * c for p, c in case2) # 混合比加权 n2 = 200000 print(f"第二题: CPI={cpi2:.4f}, MIPS={40 / cpi2:.2f}") print(f" 执行时间={n2 * cpi2 / 40 / 1000:.4f}ms")

代码里的c[1] * c[2]对应“指令数 × CPI”,用来累加总周期;p * c对应“混合比 × CPI”,两者数学本质一样。第二题执行时间算出来约 11.2ms,题目没要求,但算出来可以和 MIPS 互相验证。我在格式化输出里把cyc1 / 40的结果再除以 1000,因为周期数除以 40MHz 得到的是微秒,要转成毫秒。

4. 从两道作业题看 CPI 的构成与 Cache 缺失惩罚

4.1 CPI 不是印在 CPU 说明书上的常数

很多初学者以为 CPU 的 CPI 是个固定值,比如“酷睿 i7 的 CPI 是 0.5”,其实不对。CPI 是程序与机器共同作用的结果,同一颗处理器跑不同程序,CPI 可能差好几倍。第一题和第二题恰好展示了这个现象:同一台 40MHz 机器,第一题程序有效 CPI=1.55,MIPS 约 25.8;第二题程序平均 CPI=2.24,MIPS 约 17.9。机器没换,换的是指令混合情况。

第二题的 1、2、4、8 这组数本身就是刻意设计的性能层级:算术逻辑指令只访问寄存器,一个周期完成;Cache 命中的加载/存储多了一次命中访问,所以两个周期;转移指令在多级流水线里可能打断流水,代价更高;Cache 缺失的访存要下到主存,DDR 读一个缓存行要几十上百个周期,8 已经是简化模型。看这个序列就能体会,CPI 差异的本质来自数据访问路径的长度,而不是运算本身有多复杂。

4.2 从“混合比”到缺失惩罚模型

把第二题里的 8 拆开看,可以写成“命中访问 2 周期 + 缺失额外惩罚 6 周期”,于是平均 CPI 的表达式可以重构为:

CPI = 60%×1 + 18%×2 + 12%×4 + 10%×(2 + 6) = 2.24

后半项 10%×6=0.6,就是 Cache 缺失给每条指令摊上的额外周期。这种写法在真实系统结构分析里更常见,因为硬件设计者能分别提供命中延迟和缺失惩罚,而缺失率取决于程序访问模式。教材后续讲存储层次时,公式会变成:

CPI = 理想 CPI + 每条指令访存次数 × 缺失率 × 缺失惩罚

第二题没给“每条指令访存次数”,默认是 1,所以缺失惩罚项就是 10%×6。用代码拆这一步会更直观:

# 拆解第二题 CPI:把缺失访存指令的8周期分解为命中延迟+惩罚 base_cpi = 0.60 * 1 + 0.18 * 2 + 0.12 * 4 # 前三类,不含缺失访问 miss_rate = 0.10 miss_penalty = 8 - 2 # Cache缺失相对命中多付的周期 cpi2 = base_cpi + miss_rate * miss_penalty print(f"基准CPI={base_cpi:.2f}, 缺失惩罚贡献={miss_rate * miss_penalty:.2f}, 合计CPI={cpi2:.2f}")

输出会是基准 CPI=1.44,缺失惩罚贡献=0.60,合计 2.04——不对,这里base_cpi算出来是 1.44,0.10×6=0.60,加起来应该等于 2.04,不是 2.24。问题出在分解方式:前一类的“命中访问”CPI 其实是 2,而基础部分只算了 18%×2;缺失那类应该先归入命中访问的基础 18%×2 再叠加惩罚 6。正确拆法是把缺失率并入命中比例一起算:

load_store_hit_ratio = 0.18 + 0.10 # 命中的加载/存储 + 缺失的访存 cpi2 = (0.60 * 1 + load_store_hit_ratio * 2 # 所有访存类先按命中算 + 0.12 * 4 + 0.10 * 6) # 只有缺失部分补惩罚 print(f"CPI={cpi2:.2f}")

修改的核心是参数口径:缺失的访存指令同时属于“加载/存储”这一类,它先支付 2 个命中周期,再额外支付 6 个惩罚周期,所以 8=2+6。这个细节在作业里不重要,但在真实性能建模里决定生死——如果把缺失率的基数和命中率的基数搞混,CPI 能偏出去 10% 以上。

4.3 同一份作业里最容易翻的五个车

把常见错误汇总成一张表,每一行都是批作业时真实见到过的:

出错位置错误表现正确做法
第一题表格把整数运算 CPI 当成 2,四类全乘 2整数运算 CPI=1,总周期 155,000
第二题断句把“1 2 4 8”读成 CPI=12、4、12、12按列对齐:1、2、4、8
MIPS 单位40/2.0=20 之后再乘 10^6 写成 20,000,00020 MIPS 本身已含百万单位
混合比处理用(1+2+4+8)/4=3.75 做平均必须乘以对应比例再求和
时间换算155,000×25ns 忘记转毫秒除以 10^6 或直接用 N×CPI/f

每一类错误都能在计算中途发现:CPI 如果算出来 2.0,对照 1.55 的结果偏差 29%;MIPS 如果写成 20,000,000,用执行时间反推每秒指令数一定对不上。性能指标的检验手段永远是交叉验证。

5. 验算技巧:把两道作业题改造成可运行的基准测试

5.1 用断言把计算过程变成自动化测试

手算容易错,那就把计算规则封装成函数,再用断言锁住结果。以后拿到任何同类型表格,改数据就能跑:

def cpi_by_count(rows): """rows: [(指令数, CPI), ...],返回有效CPI""" total = sum(r[0] for r in rows) cycles = sum(r[0] * r[1] for r in rows) return cycles / total def cpi_by_ratio(rows, total_inst): """rows: [(混合比, CPI), ...],返回平均CPI""" return sum(r[0] * r[1] for r in rows) # 第一题 assert abs(cpi_by_count([(45000, 1), (32000, 2), (15000, 2), (8000, 2)]) - 1.55) < 1e-9 # 第二题 assert abs(cpi_by_ratio([(0.60, 1), (0.18, 2), (0.12, 4), (0.10, 8)], 200000) - 2.24) < 1e-9 print("两组CPI校验通过")

这里用了浮点误差阈值1e-9而不是直接比较相等,因为 0.6、0.18 这类小数在二进制浮点里并不精确。assert 失败时脚本会直接抛异常,适合在改参数后快速确认有没有破坏公式结构。

5.2 改变指令总数 N 观察不变性

一个值得动手做的实验:把第二题的总指令数从 200,000 改成 20,000、2,000,000,平均 CPI 和 MIPS 都不会变,只有执行时间线性变化。原因是指令混合比是归一化的权重,与规模无关;而执行时间=N×CPI/f,N 直接乘在前面。用前面给出的 Python 脚本改n2重新跑,能看到第一行输出不变,执行时间按 0.1 倍、10 倍变化。这个结论可以帮助理解性能指标的适用边界:CPI、MIPS 描述的是“处理器的执行效率”,执行时间才是用户真正感知的指标。

5.3 把作业题映射到 perf 统计

真实 Linux 机器上可以用perf stat采到 cycles 和 instructions 两个硬件计数器:

perf stat -e cycles,instructions ./test_program

输出里 cycles 除以 instructions 就是程序实际 CPI。常见做法是把两个数值记下来,手工除一下,或者用管道传进 awk 取第三列直接算。要注意 perf 在虚拟机、容器里可能因为缺少硬件计数器权限而报错,这时加上perf stat -e cycles:u,instructions:u只统计用户态,能规避一部分权限问题。作业题里的 40MHz 是老师给定的固定主频,而 perf 给的是真实机器上的动态频率数据,两者计算方法一致,只是数据来源不同——把作业里的表格换成 perf 输出,你就完成了从书面计算到实测基准的跨越。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询