Appearance
题目
有如下 C 语言程序段:
c
for (k = 0; k < 1000; k++)
a[k] = a[k] + 32;若数组 a 以及变量 k 均为 int 型,int 型数据占 4B,数据 Cache 采用直接映射方式,数据区大小是 1KB,块大小是 16B,该程序段执行前 Cache 为空,则该程序段执行过程中,访问数组 a 的 Cache 的缺失率是( )。
错因
A
把"总访问次数"算大了 10 倍——例如把循环误读成 10000 次(看花眼把 1000 当 10000),缺失数仍按 250 算,得 250 / 20000 = 1.25%。或者把块大小当成 32B(一块 8 个 int),1000/8 = 125 次缺失,125 / (2×1000×5) ≈ 1.25%。本质都是某一参数的数量级出错。
B
把缺失次数少算了 5 倍——比如把块大小当作 80B(误把"块大小 16B"看成"16 字",1 字 = 4B 时变成 64B;或把 1KB 与 16B 关系搞混导致每块容纳 20 个 int),结果是 50 次缺失,50 / 2000 = 2.5%。属于对"块大小如何切数组"这一步的算式记错。
D
最常见的坑:忽略了 a[k] = a[k] + 32 每次循环对 a[k] 访问 2 次(先读再写),把总访问数当成 1000,得 250 / 1000 = 25%。题面中的赋值语句 = "右边读 + 左边写" 共 2 次访存,真要算 Cache 缺失率必须把分母算对。
总解析
第一步:算"一块装多少个 int"
块大小 16B,每个 int 占 4B → 一块装 个连续的 int。即 a[0..3] 在同一块、a[4..7] 在下一块……以此类推。
第二步:算总缺失次数(按访问数组 a 的元素来数)
数组共 1000 个 int,每 4 个一块 → 块。
直接映射 + 顺序遍历 + 块容量 (1KB / 16B = 64 块) 充足的前提下,每块只在访问其第一个元素时缺失一次:
- 访问 a[0] → miss(装入 a[0..3] 整块)→ 后续 a[1]/a[2]/a[3] 都 hit
- 访问 a[4] → miss(装入 a[4..7])→ 后续 a[5]/a[6]/a[7] 都 hit
- ……
总缺失数 = 250。
第三步:算总访问次数(这是关键易错点)
a[k] = a[k] + 32 这一句:
- 等号右边:读 a[k](1 次访问)
- 等号左边:写 a[k](1 次访问)
每次循环对 a[k] 共 2 次 访问,循环 1000 次:
第四步:算缺失率
最终答案是 C(12.5%)。
逐块访问示意(以前两块为例):
| 循环 | 操作 | 命中 / 缺失 | 累计缺失数 |
|---|---|---|---|
| k=0 | 读 a[0] | miss(装入 a[0..3]) | 1 |
| k=0 | 写 a[0] | hit | 1 |
| k=1 | 读 a[1] | hit | 1 |
| k=1 | 写 a[1] | hit | 1 |
| k=2 | 读 a[2] | hit | 1 |
| k=2 | 写 a[2] | hit | 1 |
| k=3 | 读 a[3] | hit | 1 |
| k=3 | 写 a[3] | hit | 1 |
| k=4 | 读 a[4] | miss(装入 a[4..7]) | 2 |
| k=4 | 写 a[4] | hit | 2 |
| ... | ... | ... | ... |
每块 8 次访问中只有第 1 次 miss,缺失率 = 。
关键易错点:
- "分母"是访问数,不是循环数:赋值语句
x = x + c算 2 次访问。这条规则在所有 cache 题里通用。 - "分子"是块的入场缺失,不是元素数:直接映射 + 顺序访问 + cache 容量足够 → 每块只 miss 一次。
- 写入是否计为访问:是。无论 write-back 还是 write-through,对 cache 访问而言"写命中"也是一次成功访问。