cache(二)直接缓存映射

在知乎发现一份不错得学习资料 请教CPU的cache中关于line,block,index等的理解?

PPT 地址 https%3A//cs.slu.edu/%7Efritts/CSCI224_S15/schedule/chap6-cache-memory.pptx

课程主页 https://cs.slu.edu/\~fritts/CSCI224_S15/schedule/

0. 缓存定义

这张图展示了缓存的通用组织结构,通过参数 ( S )、( E )、和 ( B ) 来定义缓存的配置。具体解释如下:

  1. 缓存的组成部分

    • Set(组):缓存被分为多个组,图中每一行表示一个组。
    • Line(行):每个组包含若干行。行代表缓存中存储数据块的单元。
    • Block(块):每行包含一个数据块,数据块由多个字节组成。
  2. 参数解释

    • ( S = 2^s )(组的数量):缓存中一共有 ( S ) 个组。
    • ( E = 2^e )(每组的行数) :每个组包含 ( E ) 行,也可以称为组相联度。例如,( E = 1 ) 表示直接映射缓存,( E > 1 ) 表示组相联缓存。
    • ( B = 2^b )(每块的字节数):每个缓存块的数据量是 ( B ) 个字节。
  3. 缓存大小计算

    • 缓存的总大小 ( C ) 计算公式为:
      C = S × E × B C = S \times E \times B C=S×E×B
    • 这个公式表示缓存的容量等于组数乘以每组的行数再乘以每块的字节数。
  4. 每行的结构

    • 有效位(Valid Bit):用于指示这一行是否包含有效的数据。
    • 标记(Tag):标识主存中数据块的标记,用于在缓存中定位具体的地址。
    • 数据(Data):实际存储的数据块,每块包含 ( B ) 个字节。
  5. 工作机制

    • 当访问一个地址时,首先根据地址中的组编号定位到特定的组。
    • 然后根据标记位判断所需的数据块是否在该组中(即是否命中)。
    • 如果命中,则读取数据块中的内容;如果未命中,则需要将数据从主存加载到缓存中。

总结:图中的结构展示了缓存的典型组织方式,包括组、行和块。通过参数 ( S )、( E )、和 ( B ) 定义了缓存的结构和大小,具体到每行的结构,包括有效位、标记位和数据块。

1. 定义

这张图展示了**直接映射缓存(Direct Mapped Cache)**的一个例子,具体参数为 ( E = 1 ),即每个组(Set)只有一行(Line)。图中的假设是缓存块大小为 8 字节。

图中的关键要素

  1. 直接映射缓存的定义

    • 在直接映射缓存中,每个内存地址都唯一地映射到缓存的一个特定位置。
    • 这里的 ( E = 1 ) 表示每个组只有一行,因此每个地址只能映射到一个组内的唯一行。
  2. 缓存结构

    • 缓存被分成多个组(图中标示为 ( S = 2^s ) 个组),每个组包含一个行。
    • 每个行包含有效位(Valid Bit)、标记位(Tag)和一个数据块(大小为 8 字节,对应索引 0 到 7 的字节)。
  3. 地址分解

    • 地址被分解成三个部分:标记位(Tag)、组索引(Set Index)和块偏移量(Block Offset)。
      • 标记位(Tag):用于验证缓存中数据的唯一性,防止地址冲突。
      • 组索引(Set Index) :用于定位特定的组。在图中,地址的组索引部分显示为二进制的 0...01,用以找到正确的组。
      • 块偏移量(Block Offset) :在缓存行的数据块中定位到具体字节。图中示例的块偏移量为 100,表示需要读取数据块中的第 4 个字节(从 0 开始计数)。
  4. 地址查找流程

    • 定位组 :根据地址的组索引 0...01,系统找到特定的组。
    • 检查标记 :然后检查该组中的行是否有相应的标记位(Tag)与地址中的标记位匹配,并且有效位(Valid Bit)是否为1。
      • 如果匹配并且有效位为1,则表示缓存命中,可以直接读取数据。
      • 如果不匹配或者有效位为0,则发生缓存未命中,需要从主存加载数据。
    • 读取数据 :在缓存命中的情况下,通过块偏移量 100 从数据块的第 4 个字节开始读取数据。

例子的说明

  • 缓存大小:因为每个组只有一行(直接映射),所以在直接映射缓存中,每个内存地址只会映射到唯一的缓存位置。这种方式简单高效,但可能会导致较高的冲突率(不同的地址可能映射到同一组,导致数据被频繁替换)。
  • 示例中的地址:假设的地址中,组索引和块偏移量共同确定了访问的组和字节位置。

总结

这个例子展示了直接映射缓存的读取过程。通过地址的组索引定位组,通过标记位验证缓存行的有效性,并利用块偏移量定位到具体数据的位置。直接映射缓存的优点是结构简单,但可能因为冲突而降低缓存效率。

2. set中第二行表示

这张图展示了直接映射缓存中发生**缓存未命中(Cache Miss)**时的数据替换过程。具体描述如下:

图中步骤和流程

  1. 初始状态

    • 上半部分显示了当前缓存行的状态。
    • 地址被分解为三个部分:标记位(Tag)、组索引(Set Index),以及块偏移量(Block Offset)。
    • 处理器要访问一个整数(int),地址中指定的组和块偏移量指向了缓存的一个位置。
    • 缓存行中的有效位(Valid Bit)显示该行是有效的,并且标记位用来判断数据是否匹配。
    • 在此情况下,假设此地址的标记不匹配当前缓存行的标记位,因此发生了缓存未命中(No match)。
  2. 缓存未命中的处理

    • 缓存未命中意味着当前请求的数据块不在缓存中,因此需要从主存(Memory)中加载该数据块。
    • 根据直接映射缓存的策略,未命中时,缓存会将当前行的数据逐出(Evict),并将新数据块加载进来。
    • 下半部分展示了替换后的缓存行,其中原有的数据被新数据块替换。
  3. 加载新数据块

    • 新数据块加载到缓存后,标记位更新为新数据块的标记,表示这行缓存现在存储的是新的数据块。
    • 有效位依然为1,表明这一行现在包含有效的数据。
    • 偏移量指向数据块中的具体字节位置,从该位置读取数据。
    • 在这个例子中,整数数据(int)被存储在数据块的第4到第7字节(图中标注为绿色)。
  4. 总结

    • 缓存未命中导致缓存中的数据被替换为新的数据块。
    • 替换后,处理器可以根据偏移量读取到所需的数据。
    • 直接映射缓存的特点是每个组只有一个行,因此一旦发生未命中,当前行的数据就会被新数据块替换,容易导致冲突。

图中标注的说明

  • No match:上半部分红色文字表示缓存未命中。
  • Old line is evicted and replaced:未命中后,原有的缓存行被逐出,并由新数据块替换。
  • Block offset:块偏移量用于确定数据块中的具体字节位置。

总结

这个图例展示了直接映射缓存中未命中的处理流程。在未命中时,旧数据被替换,新数据块被加载到缓存行中,并更新了标记位和数据。这样,处理器可以在缓存中读取到最新的数据块。

3. 例子

这张图展示了一个具有特定配置的直接映射缓存示例,详细描述了缓存初始化、地址访问轨迹(Address Trace)及缓存命中或未命中情况,并展示了最终的缓存配置。具体说明如下:

配置参数

  • M = 16 字节地址空间:主存大小为 16 字节。
  • B = 2 字节/块:每个缓存块包含 2 字节。
  • S = 4 个组(Sets):缓存有 4 个组。
  • E = 1 块/组:每个组只有 1 个块,即直接映射缓存。

地址分解

  • 地址被分为三部分:
    • t = 1:1 位的标记位(Tag)。
    • s = 2:2 位的组索引(Set Index)。
    • b = 1:1 位的块偏移量(Block Offset)。

初始缓存配置

  • 图中右上角显示了缓存的初始配置。
  • 初始状态下,仅组 0 包含有效数据,标记位为 1,且该块存储的是主存中地址 8 和 9 的数据,即 M[8-9]

地址访问轨迹(Address Trace)

按顺序访问一系列地址,并记录每次访问的结果(命中或未命中):

  1. 地址 0 ([0000]_2):未命中(Miss)。

    • 计算组索引为 0,标记为 0。
    • 因为当前组 0 的标记为 1,不匹配,因此未命中。
    • 加载主存地址 0 和 1 的数据块 M[0-1] 到组 0,并更新标记位为 0。
  2. 地址 1 ([0001]_2):命中(Hit)。

    • 同样映射到组 0,标记为 0,与缓存中的标记匹配。
    • 因此直接命中。
  3. 地址 7 ([0111]_2):未命中(Miss)。

    • 计算组索引为 3,标记为 0。
    • 组 3 为空,因此未命中。
    • 加载主存地址 6 和 7 的数据块 M[6-7] 到组 3,标记位更新为 0。
  4. 地址 8 ([1000]_2):未命中(Miss)。

    • 计算组索引为 0,标记为 1。
    • 组 0 中的标记为 0,不匹配,因此未命中。
    • 替换组 0 中的内容,加载主存地址 8 和 9 的数据块 M[8-9],并将标记位更新为 1。
  5. 地址 0 ([0000]_2):未命中(Miss)。

    • 组索引为 0,标记为 0。
    • 当前组 0 的标记为 1,不匹配,因此未命中。
    • 替换组 0 中的内容为 M[0-1],标记位更新为 0。
  6. 地址 A ([1010]_2):未命中(Miss)。

    • 组索引为 1,标记为 1。
    • 组 1 为空,因此未命中。
    • 加载主存地址 10 和 11 的数据块 M[10-11],标记位更新为 1。
  7. 地址 6 ([0110]_2):命中(Hit)。

    • 组索引为 3,标记为 0,与缓存中的标记匹配,因此命中。

最终缓存配置

图中右下角显示了最终的缓存状态:

  • Set 0 :有效位 1,标记位 0,包含 M[0-1]
  • Set 1 :有效位 1,标记位 1,包含 M[10-11]
  • Set 2:无数据(有效位 0)。
  • Set 3 :有效位 1,标记位 0,包含 M[6-7]

总结

此示例展示了直接映射缓存的工作过程,包括地址分解、命中判断和替换。

3.1 最后一位作用

在这个缓存配置示例中,最后一位(即 块偏移量(block offset) 位)确实没有参与缓存行选择标记匹配过程。具体原因如下:

地址位分解

  • 这里的地址结构如下所示:
    • t = 1:1 位的标记(Tag),用于判断数据是否在缓存中。
    • s = 2:2 位的组索引(Set Index),用于选择缓存中的特定组。
    • b = 1:1 位的块偏移量(Block Offset),用于在缓存块内定位特定字节。

块偏移量的作用

  • 块偏移量 用来定位缓存块内的具体字节,但不会参与组选择或标记匹配
  • 在本例中,每个块大小为 2 字节,因此块偏移量 b = 1 位,表示可以选择块中的两个字节之一。
    • 例如,地址 00000001 都会映射到相同的缓存组和标记,因为它们的前 3 位相同,最后一位(块偏移量)只是选择块内的具体字节。

为什么块偏移量不用于组选择

  • 组选择 只取决于组索引位
  • 标记匹配 只取决于标记位
  • 块偏移量的作用仅限于定位块内的具体字节,并不用于决定数据在缓存中的位置或是否命中。

总结

块偏移量(最后一位)在此示例中并未用于决定是否命中或替换,而仅用于确定缓存块中的具体字节位置。

4. 代码

这个图展示了在缓存中访问二维数组的两种不同方式对性能的影响。通过 sum_array_rowssum_array_cols 两种函数,展示了按行访问和按列访问的区别。

缓存的假设配置

  • 每个缓存组包含一个块(One block per set)。
  • 每个块可以存储 8 个 double 类型的数据(即 64 字节,因为一个 double 占用 8 字节)。
  • 初始状态为冷缓存(cold cache),即缓存是空的。

函数分析

  1. sum_array_rows 函数(按行访问)

    c 复制代码
    int sum_array_rows(double a[16][16]) {
        int i, j;
        double sum = 0;
        for (i = 0; i < 16; i++)
            for (j = 0; j < 16; j++)
                sum += a[i][j];
        return sum;
    }
    • 访问模式 :按行访问,即 a[i][j],对于每个 ij 从 0 到 15 遍历。这意味着程序会连续访问数组的一行内的元素。
    • 缓存效果 :由于每个块可以存储 8 个 double,当访问 a[i][0] 时,a[i][0]a[i][7] 会被加载到同一个缓存块中。
      • 当访问到 a[i][1]a[i][7] 时,数据已经在缓存中,命中率较高。
      • 然后访问 a[i][8] 时,会加载 a[i][8]a[i][15],这段访问也会命中。
    • 结果:按行访问时,缓存命中率较高,效率较高。
  2. sum_array_cols 函数(按列访问)

    c 复制代码
    int sum_array_cols(double a[16][16]) {
        int i, j;
        double sum = 0;
        for (j = 0; j < 16; j++)
            for (i = 0; i < 16; i++)
                sum += a[i][j];
        return sum;
    }
    • 访问模式 :按列访问,即 a[i][j],对于每个 ji 从 0 到 15 遍历。这意味着程序会在一列内访问元素。
    • 缓存效果 :由于每次访问时跳到下一行的元素,a[i][j]a[i+1][j] 不在同一个缓存块中。
      • 每次访问会导致缓存未命中,因为 a[i][j]a[i+1][j] 属于不同的块。
    • 结果:按列访问时,缓存命中率低,效率较差。

总结

  • 按行访问sum_array_rows)会充分利用缓存,使连续的数据加载到同一个缓存块中,从而提高缓存命中率。
  • 按列访问sum_array_cols)导致缓存不命中频繁发生,因为每次访问跳到不同的块,无法充分利用缓存。
  • 因此,二维数组的访问方式对缓存命中率有显著影响。通常情况下,按行访问效率更高,特别是在直接映射缓存或低相联度缓存中。
相关推荐
2301_793086876 小时前
Redis 04 Reactor
数据库·redis·缓存
1892280486110 小时前
NY243NY253美光固态闪存NY257NY260
大数据·网络·人工智能·缓存
青鱼入云11 小时前
redis怎么做rehash的
redis·缓存
FFF-X11 小时前
Vue3 路由缓存实战:从基础到进阶的完整指南
vue.js·spring boot·缓存
夜影风1 天前
Nginx反向代理与缓存实现
运维·nginx·缓存
编程(变成)小辣鸡1 天前
Redis 知识点与应用场景
数据库·redis·缓存
菜菜子爱学习2 天前
Nginx学习笔记(八)—— Nginx缓存集成
笔记·学习·nginx·缓存·运维开发
魏波.2 天前
常用缓存软件分类及详解
缓存
yh云想2 天前
《多级缓存架构设计与实现全解析》
缓存·junit
白仑色2 天前
Redis 如何保证数据安全?
数据库·redis·缓存·集群·主从复制·哨兵·redis 管理工具