您有可以引用 RAM 中字节的 32 位地址。
您希望能够缓存您访问的数据,以便以后使用。
假设您想要一个 1-MiB(220 字节)的缓存。
你是做什么的?
您需要满足 2 个限制:
- 所有地址的缓存应尽可能统一。即您不想偏向任何特定类型的地址。
- 你是怎么做到的?使用余数!使用 mod,您可以将 any 整数均匀分布在您想要的任何范围内。
- 您想帮助尽量减少簿记成本。这意味着例如如果您在 1 个字节的块中进行缓存,您不想存储 4 个字节的数据只是为了跟踪 1 个字节的所属位置。
假设您选择 16 字节(24-byte)块。这意味着您可以缓存 220 / 24 = 216 = 65,536 个数据块。
您现在有几个选择:
- 您可以设计缓存,以便任何内存块中的数据都可以存储在任何个缓存块中。这称为全关联缓存。
- 好处是它是“最公平”的缓存类型:所有块都被完全平等地对待。
- 权衡是速度:要找到放置内存块的位置,您必须搜索 每个 缓存块以获得可用空间。这真的很慢。
- 您可以设计缓存,使来自任何内存块的数据仅存储在单个缓存块中。这称为直接映射缓存。
- 好处是它是最快的缓存类型:您只需要检查项目是否在缓存中。
- 折衷方案是,现在,如果您碰巧有一个错误的内存访问模式,您可以让 2 个块连续地相互踢出,而未使用的块仍保留在缓存中。
- 您可以混合使用这两种方法:将单个内存块映射到多个块。这就是真正的处理器所做的——它们有 N 路组关联缓存。
直接映射缓存:
现在您有 65,536 个数据块,每个数据块有 16 个字节。
您将其作为 65,536 个“行”存储在缓存中,每个“行”由数据本身以及元数据(关于块所属的位置、是否有效、是否已写入等)组成。
问题:
内存中的每个块如何映射到缓存中的每个块?
回答:
好吧,您正在使用直接映射缓存,使用 mod。这意味着地址 0 到 15 将映射到缓存中的块 0; 16-31 被映射到第 2 块,等等......当你到达 1-MiB 标记时它会环绕。
那么,给定内存地址 M,你如何找到行号 N?简单:N = M % 220 / 24。
但这只会告诉您在哪里存储数据,而不是如何检索它。一旦你存储了它,并尝试再次访问它,你必须知道哪个这里存储了 1-MB 的内存部分,对吗?
这就是元数据的一部分:标签位。如果它在第 N 行,那么您只需要知道在 mod 操作期间 quotient 是什么。其中,对于 32 位地址,大 12 位(因为余数是 20 位)。
所以你的标签变成了 12 位长——具体来说,是任何内存地址的最上层 12 位。
而且您已经知道,最低 4 位用于偏移在一个块(因为内存是字节寻址的,一个块是 16 个字节)。
这为内存地址的“索引”位留下了 16 位,可用于查找该地址所属的 行。 (这只是一个除法+余数运算,但是是二进制的。)
您还需要其他位:例如您需要知道一个块实际上是否有效,因为当 CPU 开启时,它包含无效数据。所以你添加了 1 位元数据:有效位。
您将了解其他一些内容,用于优化、同步等......但这些是基本的。 :)