CPU Cache 的映射与寻址

1、CPU Cache 的组织结构

CPU Cache 的组织结构:CPU Cache 被划分成多个组 Set,每个 Set 中还可以有多个行 Cache Line,需要注意的是 Cache Line 是 CPU Cache 中的基本缓存单位,也有人叫它块(Block),也就是说它每次读写不是一个字节的去读写,而是以 Cache Line 为单位,一块块地去读取。

Cache Line 由有效标志 (Valid)、标志 (Tag)、数据块 (Data)这3个部分组成,其中 data 是真正要来缓存一片连续内存地址中的数据,而 tag 是用来查找 Cache Line 的标志,存储这片连续数据的公共地址,valid 表示当前缓存的数据是否有效,也可以用来协助查找 Cache Line。

2、Cache 与内存地址映射

我们知道高速缓存的速度要远远快于主存(内存)的速度,但 Cache 的容量却是远远小于主存,相较于主存的价格,缓存则昂贵的多

另一方面 CPU Cache 是需要缓存内存中的数据,无论对 Cache 数据读取还是写入,CPU 都需要知道访问的内存数据,对应于 Cache 上的哪个位置,而由于 Cache 容量不够,无法做到 Cache 与内存地址一一对应

那么 Cache 是如何与内存地址进行映射的?

其实 CPU Cache 主要有 3 种的实现方式:

  • 直接映射(direct-mapped),也就是每组 Set 只有一个 Cache Line,选中 Set 之后不需要和 Set 中的每个 Line 进行比对
  • 全相连映射(fully associative),即只有一个 Set,所以这个 Set 中包含所有的 Line,需要逐个进行对比
  • 组相连映射(set-associative ), 有多个 Set,每个 Set 有多个 Line;需要注意的是,组相连也会被称为路 Way,比如 6 路组相联,表示每个 Set 有 6 个 Line

2.1 直接映射

直相联映射,每个 Block 只对应一个 Cache line。这种映射方式类似于按照 Main Memory 的 Block 编号,对 Cache line 的数量进行取模,得到映射的 Cache line。

它的优点是,这种映射策略硬件实现简单,因为它不需要搜索所有的 cache line(每一个 Block 对应一条Cache line),它的缺点是,由于每个 set 只有一条 cache line,每个 Block 也只对应相应的 Cache line,因此,Cache 的占用率和命中率都不高(比如下图的 cache line 0 只能给 Block0、Block64、Block128...使用,对于 Block2-Block63,无法使用 cache line 0)。

直接映射的特点如下

  • cache 被组织成多个 sets(组),每个 set 只有一个 cache line
  • 每个 Main Memory 的地址(Block)只对应一个 Cache line(由 cpu 发出地址中的 index 字段判断)
  • 地址中 Tag 的作用,是为了在 index 字段相同的情况下,区分不同的 Block。
  • 地址中 offset 的作用,是为了在一个 Cache Line 内寻址。由于 cache line size 是 4 Bytes,因此 offset 需要 2 bits

我们现在思考一个问题,如果一个程序试图依次访问地址 Block0、Block64、Block128,cache 中的数据会发生什么呢?

首先我们应该明白 Block0、Block64、Block128 地址中 index 部分是一样的。因此,这 3 个地址对应的 cache line 是同一个。

所以,当我们访问 Block0 地址时,cache 会缺失(cache miss),然后数据会从主存中加载到 cache 中第 0 行 cache line。

当我们访问 Block64 地址时,依然索引到 cache 中第 0 行 cache line,由于此时 cache line 中存储的是地址 Block0 地址对应的数据(对比 Tag 不同),所以此时依然会 cache 缺失。然后从主存中加载 Block64 地址数据到第一行 cache line 中。

同理,继续访问 Block128 地址,依然会 cache 缺失。这就相当于每次访问数据都要从主存中读取,所以 cache 的存在并没有对性能有什么提升。访问 Block64 地址时,就会把 Block0 地址缓存的数据替换,这种现象叫做 cache 颠簸(cache thrashing)。

2.2 全相连映射

全相联映射,Main Memory 的每个 Block 都可以放在任意一个 Cache line 中,映射方式比较灵活,Cache 的利用率高,块冲突的概率低(cache miss),因为只有当所有的 Line 都被占满后才会出现冲突。

全相连映射的特点如下

  • 全相联映射中,所有的 Cache 被组织成一个 Set(所以地址中不需要 index 段),但这个 Set 中有多条 Cache line
  • 一个 Main Memory Block 可以对应到任意 Cache line 中。如下图所示,Block 0 可能映射到任意 Cache line

这种映射的优点是它能够提供灵活的放置策略,一个 Block 可以映射到任意 Cache line 上,因此,命中率较高。但它的缺点是在搜索时,它需要遍历所有的 cache line 以找到空闲的或者合适的块进行替换,硬件的比较也比较复杂。

2.3 组相连映射

组相联映射是直接映射和全相联映射的折中方案,吸取 2 者的优点,尽量避免 2 者的缺点,组间采用直接映射,组内采用全相联映射,即主存块存放到哪个组 set 是固定的,存到 set 内哪一个 cache Line 是灵活随意的。

组相连映射的特点如下

  • cache 被分为 n 个 set,每个 set 包含 m 条 cache line
  • 一个 Main Memory Block 首先被映射到一个 set 中,然后可以被放到这个 set 的任意一条 cache line 中

很明显,这种映射方式拥有直相联映射和全相联映射的优点,既能够提供灵活的放置策略,硬件实现起来也不复杂。所以这种方式在现代的处理器中得到了广泛的应用。

3、Cache 的寻址方式


四路组相连

  • way:组相联 cache 将 cache 分成大小相等的几片,每一片就叫一个 way(路)。比如一般芯片 L1 cache 大小为 32KB,4 路组相联,那么每个 way 的大小就是 32KB/4=8KB
  • index:每个 way 是由多行 cache line 组成的,index 就是用来索引一个 way 中哪一行 cache line。比如 way 大小 8KB,cacheline 为64B,那么每个 way 就有 128 行 cache line,index 取值就在0-127
  • set:不同 way 中 index 相同的 cache line 的集合称作一个set
  • tag:是内存地址的高位部分,存储在 cache 中用于标识相应的数据。一般来说 cache 的大小指的是所保存的数据规模,并不包括其中需要储存 tag 的物理空间
  • offset:前面我们提到过,一个 cache line 由多个数据组成,有时候你不需要整个 cache line 数据,这个时候可以根据其中的地址低位作为 offset 进行索引 cache line 中各个数据

那么 Cache 寻址的具体方式,我们可以将其分为 3 个步骤(都是由硬件自己完成):

  • 先根据地址中的 index 来找到对应的 set
  • 接着根据 tag 在上一步找到的 set 中找到对应的 cache Line,如果找到且对应的有效位 valid 为 1,表示缓存命中,反之无论其中的 tag 和 cache Line 里的数据内容是什么,CPU 都不会管这些数据,而是会直接访问主存,重新加载数据(当然这里涉及到缓存一致性的问题,比较复杂,本文暂不讲解),那如果没找到,说明当前发生 cache 缺失,即 cache miss
  • 最后根据 offset 在上一步找到的 cache Line 的 data 中找到对应内存的数据

4、VIVT、VIPT、PIPT

我们都知道 cache 控制器根据地址查找判断是否命中,这里的地址究竟是虚拟地址(virtual address,VA)还是物理地址(physical address,PA)?我们应该清楚 CPU 发出对某个地址的数据访问,这个地址其实是虚拟地址,虚拟地址经过 MMU 转换成物理地址,最终从这个物理地址读取数据。因此 cache 的硬件设计既可以采用虚拟地址也可以采用物理地址甚至是取两者地址部分组合作为查找 cache 的依据:

  • VIVT(Virtual Index Virtual Tag):使用虚拟地址索引域和虚拟地址的标记域;
  • PIPT(Physical Index Physical Tag):使用物理地址的索引域和物理地址的标记域;
  • VIPT(Virtual Index Physical Tag):使用虚拟地址的索引域和物理地址的标记域;

4.1 VIVT

步骤

  1. CPU 把虚拟地址送到 cache
  2. 使用虚拟地址的 index 和 tag 定位缓存行
  3. 如果缓存命中,则使用缓存中的数据;
  4. 如果缓存不命中,则把虚拟地址发给 MMU,经过 MMU 转换为物理地址,访问内存。

优点

  • 硬件设计简单
  • 若能缓存命中,那么不需要 MMU 把虚拟地址转换为物理地址的过程,提升了 cache 的访问速度

缺点

  • 引入软件上的问题------操作系统在管理 cache 的时候,主要会遇到两个问题:
    • 歧义(ambiguity):相同的虚拟地址映射不同的物理地址就会出现歧义。例如:两个进程的某一相同虚拟地址指向了不同的物理地址,进程切换时,可能读到别人的旧数据。操作系统为了避免 VIVT 歧义,在进程切换时,可以 flush 所有的 cache,使主存储器有效,高速缓存无效。后果就是切换后的进程会有大量的 cache miss 导致性能损失
    • 别名(alias):在 2 个虚拟地址对应同一个物理地址时(进程 a 中某物理地址被映射为虚拟地址 c、进程 b 中上述同样的物理地址被映射为虚拟地址 d),它们完全可能在 2 个 cache line 同时命中(修改一个、另一个拿到的还是旧数据),此时就出现了别名问题。别名的问题也可以通过进程切换时,flash 所有 cache 去解决
      • Icache 虽然也会产生别名问题,但因为 iCache 是只读的(cpu 不会改写 I-cache 中的数据),所以即使两个 cache line 缓存一个物理地址上的指令,也不存在问题
  • 这两个问题的软件维护成本极高,最难管理,现在基本没有硬件还在使用了

4.2 PIPT

步骤

  1. CPU 把虚拟地址送给 MMU,经过 MMU 转换为物理地址,送到 cache
  2. 使用物理地址的 index 和 tag 定位缓存行
  3. 如果缓存命中,则使用缓存中的数据;
  4. 如果缓存不命中,则直接根据物理地址访问内存

优点

  • 物理地址是唯一的,不会产生歧义和别名

arm 芯片从 cortex-a 开始基本都是 pipt 的 cache

缺点

  • 不管是否 cache 命中,都会经过 MMU 转换地址,性能最差

4.3 VIPT

步骤

  1. 虚拟地址的 index 部分进行 cache 查找
  2. 同时,虚拟地址送到 MMU 转换成物理地址(硬件上可以并行进行)
  3. 1,2 完成后,此时比较物理地址的 tag 部分定位缓存行
  4. 如果缓存命中,使用缓存中的数据;
  5. 如果缓存不命中,也可以直接使用物理地址访问内存。

优点

  • 查找 cache index 部分和 MMU 转换地址 同时进行
  • VIPT 使用物理地址部分作为 tag,不会存在歧义问题
  • 可能存在别名问题

当 index + offset <= pagesize 时,不会出现别名问题。因为 pageoffset 部分虚拟地址和物理地址是相同的。所以这时 VIPT == PIPT

缺点

  • 不能完全避免别名问题
相关推荐
爬山算法16 分钟前
MySQL(84)如何配置MySQL防火墙?
android·数据库·mysql
拾荒的小海螺19 分钟前
MySQL:SQL 慢查询优化的技术指南
数据库·sql·mysql
咖啡续命又一天20 分钟前
Linux grep 命令
linux·运维
小高求学之路21 分钟前
Centos 离线部署(MQTT)EMOX脚本并设置开机自启
linux·运维·centos
爬山算法22 分钟前
MySQL(83)如何设置密码复杂度策略?
android·数据库·mysql
码农101号31 分钟前
Linux中ansible模块补充和playbook讲解
linux·运维·ansible
weixin_4569042734 分钟前
SQLite 数据库操作完整指南
数据库·sqlite
漫步者TZ1 小时前
【StarRocks系列】StarRocks vs Mysql
数据库·starrocks·mysql·分布式数据库
Java初学者小白1 小时前
秋招Day14 - MySQL - SQL优化
java·数据库·sql·mysql
运维技术分享与探索1 小时前
Centos7.6内网离线安装Docker环境和Portainer-博客
linux·运维·docker·容器·portainer