by 池雨泉
判断自己是否理解了什么是局部性原理,很简单:能不能用大白话把时间局部性和空间局部性解释出来。
- 时间局部性:相同的数据很快会被再次访问
- 空间局部性:附近的数据很快会被访问
个人认为,将局部性分成时间 (temporal) 局部性和空间 (spatial) 局部性,具有一定的误导性。因为
- 时间局部性的定义中也涉及空间 (same location/address will presumably be accessed soon)
- 空间局部性的定义中也涉及时间 (nearby locations will presumably be accessed soon)
因此,不必教条地理解 temporal 和 spatial 这两个词。
16 字节的 cache 块中可以存放
for (I = 0; I < 8; I++)
for (J = 0; J < 8000; J++)
A[I][J] = B[I][0] + A[J][I]
首先我们需要列举出这段代码中的所有变量/位置
- 外层循环变量
I
- 内层循环变量
J
- 数组 A 的基地址
A
- 数组 A 中的元素
A[I][J]
- 数组 A 中的元素
A[J][I]
- 数组 B 的基地址
B
- 数组 B 中的元素
B[I][0]
然后,注意到前一小问中告诉我们 1 个 cache 块可以存放 4 个 32 位整数。
由于题干中没有明确说明,因此我们假设数组 A 和 B 的元素均为 32 位整数。
结合时间局部性的定义可知,上述代码对以下变量/位置的访问均具有时间局部性特征
- 外层循环变量
I
- 内层循环变量
J
- 数组 A 的基地址
A
- 数组 B 的基地址
B
- 数组 B 中的元素
B[I][0]
变量 I
的时间局部性体现为
- 每次外层循环结束后的自增
- 每次到内存中访问
A[I][J]
等元素时,需要引用I
计算该元素的地址
同理,变量 J
也具有时间局部性。
变量 A
的时间局部性体现为
- 每次到内存中访问
A[I][J]
等元素时,需要引用A
计算该元素的地址
同理,变量 B
也具有时间局部性。
最后,考虑内层循环中的赋值语句
A[I][J] = B[I][0] + A[J][I]
**注意,在内层循环中,变量 I
的值始终保持不变。**因此,每次计算 A[I][J]
的新值时都需要引用 B 中的同一个元素 B[I][0]
,可见对该元素的访问也具有明显的时间局部性特征。
由于
- 时间局部性关注的是对相同位置的访问
- 空间局部性关注的是对相邻位置的访问
因此二者是互斥的。
于是我们只需考虑如下的两个访问位置
- 数组 A 中的元素
A[I][J]
- 数组 A 中的元素
A[J][I]
实际上是两对关系
- 数组 A 中同一行的两个相邻元素
A[I][J]
和A[I][J+1]
- 数组 A 中同一列的两个相邻元素
A[J][I]
和A[J+1][I]
由于 C 语言采用行优先存储,即数组的同一行中的元素在内存中连续存放,因此对 A[I][J]
的访问显示出空间局部性。
Note
本题题干表述不清,作答时言之成理即可。
for I=1:8
for J=1:8000
A(I,J)=B(I,1)+A(J,I);
end
end
既然题干说的是矩阵计算,因此我们假设数组 A
- 每一行的元素个数均相等
- 每一列的元素个数也相等
即不会出现锯齿状数组的情况。
由于
- 形如
A(I,J)
的所有元素中,地址最小的是A(1,1)
,地址最大的是A(8,8000)
- 形如
A(J,I)
的所有元素中,地址最小的是A(1,1)
,地址最大的是A(8000,8)
- 形如
B(I,1)
的所有元素中,地址最小的是B(1,1)
,地址最大的是B(8,1)
因此
- 数组 A 至少需要有 8000 行和 8000 列。
- 数组 B 至少需要有 8 行和 1 列。
于是
- 数组 A 至少存放了
$8000 \times 8000 = 64,000,000$ 个整数,共需要$\frac{64,000,000}{4} = 16,000,000$ 个 cache 块。 - 数组 B 至少存放了
$8 \times 1 = 8$ 个整数,共需要$\frac{8}{4} 2$ 个 cache 块
因此一共需要
与第 2 小问相同。访问以下变量/位置时会显示出时间局部性:
- 外层循环变量
I
- 内层循环变量
J
- 数组 A 的基地址
A
- 数组 B 的基地址
B
- 数组 B 中的元素
B[I][0]
由于 Matlab 采用列优先存储,因此与第 3 小问相反,显示出空间局部性特征的是 A[J][I]
。
此外,每次外层循环的变量 I
的值发生改变时,对 B[I][0]
的访问也具有空间局部性。
31 10 9 5 4 0
+-------------------+-------+--------+
| tag | index | offset |
+-------------------+-------+--------+
offset 字段一共有 5 位 (0,1,2,3,4)
其中低 2 位 0,1 用于表示字内地址
剩下的 3 位决定了 cache 块的大小为
cache 中的条目/项 (entry) 数是由 index 字段的位数决定的
本题中 index 字段一共 5 位 (5,6,7,8,9)
因此 cache 中一共有
cache 的每一个 entry 除了要存储数据之外,还需要存储用于管理数据的元数据
本题中,一个 entry 有如下几部分构成
-
$8 \times 32 = 256$ 个比特,其中- 8 是一个 cache 块所包含的字的个数
- 32 是一个字所包含的比特数
- 22 比特的 tag
- 有效位 (valid bit)
因此,总位数与数据位数之比等于
Note
如果忘记考虑有效位,得到的结果是
怎么画这个表
- 将每次访问的地址展开为二进制,即可得到对应的 tag/index/offset 字段
- 第一次访存时 cache 是空的,因此一定 miss
- 相同 index 相同 tag 则命中
- 相同 index 不同 tag 则需要置换出之前的 cache 块
一共 12 次访存,命中 4 次,因此命中率等于
要获得 cache 的最终状态,顺着上表捋一遍即可:从下往上看,对于每一个索引值,只要出现过一次,就不用再往上看了。
<index, tag, data>
<0, 3, Mem[0xc00]-Mem[0xc1f]>
<4, 2, Mem[0x880]-Mem[0x89f]>
<5, 0, Mem[0x0a0]-Mem[0x0bf]>
<7, 0, Mem[0x0e0]-Mem[0x0ff]>
Mem[0xc00]-Mem[0xc1f]
表示主存 (memory) 中从编号为 0xc00
的字节开始,到编号为 0xc1f
的字节结束,一共 32 个字节的数据。下同。
Note
之所以要按照索引值排序,是因为索引值实际上就是 cache 中每个 entry 的编号。
Note
不必过于纠结这道题。题干第一句话说要探讨容量对性能的影响,但是这道题失败的地方在于,计算的过程完全不涉及 cache 的容量。
送分题。
- P1 的时钟频率等于
$\frac{1}{0.66} = 1.52$ GHz - P2 的时钟频率等于
$\frac{1}{0.90} = 1.11$ GHz
沿用第 1 小问的假设,即 L1 cache 的命中时间等于处理器的时钟周期。
于是需要将访存时间换算成周期数
- 对 P1 来说,访问一次内存需要
$\lceil \frac{70}{0.66} \rceil= 107$ 个时钟周期 - 对 P2 来说,访问一次内存需要
$\lceil \frac{70}{0.90} \rceil= 78$ 个时钟周期
将已知信息代入公式得
- P1 的 AMAT 等于
$1 + 0.08 \times 107 = 9.56$ 个时钟周期,也就是$9.56 \times 0.66 = 6.31$ ns - P2 的 AMAT 等于
$1 + 0.06 \times 78 = 5.68$ 个时钟周期,也就是$5.68 \times 0.90 = 5.11$ ns
以 P2 为例。
在不考虑 cache 缺失时,由于 CPI 等于 1,根据 CPI 的定义,执行一条指令需要 1 个时钟周期。
现在考虑指令 cache 的缺失,根据已知条件,有 6% 的指令在取指阶段发生了缺失,需要额外的 78 个周期去访问内存。
进一步考虑数据 cache 的缺失,已知有 36% 的指令需要访问数据存储器,这些指令中的 6% 在访存时发生了缺失。
综上,P2 的 CPI 等于
由于 P2 的时钟周期等于 0.9 ns,因此执行一条指令平均需要
同理得,P1 的 CPI 等于
因而 P1 执行一条指令平均需要
所以 P2 更快。
Note
本题中假定指令 cache 和数据 cache 的缺失率相同,实际情况中应该将二者分开考虑。
Note
通过这道题目我们发现在评估存储系统的性能时,AMAT 和 CPI 是两个不同的指标。
访问一次 L2 cache 需要
每次访存至少需要 1 个时钟周期。
其中的 8% 会发生 L1 cache 的缺失,因而需要额外的 9 个时钟周期访问 L2 cache。
对 L2 cache 的访问中,95% 会再次缺失,需要额外的 107 个时钟周期访问主存。
因此 AMAT 等于
与第 2 小问相比,增加 L2 cache 之后,AMAT 变差了。
与第 3 小问的思想相同。
Note
之所以可以使用形如
如果将上面标红的 0.08 改成其他的值,就需要考虑指令 cache 缺失率与数据 cache 缺失率的比值问题。
只需求解一个简单的不等式
解得
本质上还是一个简单的不等式求解问题,没有太多需要注意的,只是它比较繁琐,所以需要细心
我自己的习惯是,为了确保计算的精度,在最后一步之前统一用精确值进行计算
>>> (5.96832 - 9 * 1.36 * 0.08 * 0.66) / (1.36 * 0.08 * 0.66 * 107)
0.6926633015143185
最终结果为
Note
在这里,“舍”比“入”更加合理,如果写成