13015 计算机系统原理 第五章 程序的存储访问

1,006 阅读29分钟

记录学习自考课程编码13015学习过程,献给每一位拥有梦想的"带专人"

ps:有不正确的地方麻烦更新在评论区,我会一一修复 😅

第五章 程序的存储访问

存储器概述

  1. 按使用材料分类:半导体存储器、磁表面存储器、光介质存储器
  2. 按信息可更改性分类:读/写存储器、只读存储器(ROM)
  3. 按断电后可保存性分类:非易失性存储器(ROM、磁盘)、易失性存储器(主存、Cache)
  4. 随机存取寄存器 RAM、只读存储器 ROM:都采用随机存取方式进行访问
  5. 主存储器由 DRAM(动态 RAM)芯片组成、Cache 由 SRAM(静态 RAM)芯片组成
  6. CPU 跟主存交换信息、主存跟外部辅助存储器(辅存)交换信息
  7. 磁带和光盘容量大速度慢,被用作海量后备存储器

主存储器的组成和基本操作

由存储 0 或 1 的记忆单元(Cell)构成的存储阵列是存储器的核心部分。这种记忆单元也称为存储元位元,它是具有两种稳态的能表示二进制 0 和 1 的物理器件

由多个存储元构成的称为存储阵列也称存储体存储矩阵

对各存储单元进行编号的方式称为编址方式,可以按字节编址,也可以按字编址。

编址单位:具有相同地址的那些位元构成的一个单位,可以是一个字节(按字节编址)或一个字(按字编址)。

现在大多数通用计算机都采用按字节编址方式,即存储体内一个地址中有一个字节

image.png

图中的存储器数据寄存器(MDR)存储器地址寄存器(MAR)属于 CPU 中的总线接口部件。实际上,CPU 并非与主存芯片直接交互,而是先与主存控制器交互,再由主存控制器来控制主存芯片进行读/写。现代处理器一般采用 DRAM作为主存,因此主存控制器也成为DRAM控制器

图中连到主存的数据线有 64 位,在字节编址方式下,每次最多可取 8 个单元。地址线的位数决定了主存地址空间的最大可寻址范围,如 36 位地址的最大寻址范围为0~2^36^-1,地址从 0 开始编号

存储器的层次化结构

存储器容量:能存放的二进制位数或字节数

存取时间:存储器访问时间,访问一次数据所用的时间

存储体系:在计算机中将各种不同容量和不同存取速度的存储器按一定的结构有机结合,以形成层次化存储结构,使得整个存储系统在速度、容量、价格方面具有较好的综合指标

主存块:在 Cache 和主存之间传送的,大小通常为几十字节

:主存与硬盘之间传送,大小通常为几千字节以上

image.png

图中给出典型存取时间和存储容量虽然会随时间变化,但这些数据仍能反映速度和容量之间的关系,以及层次化结构存储器的构成思想。速度越快容量越小,越靠近CPU

靠近 CPU 的相邻层之间传送单位小,原理 CPU 的相邻层之间传送单位大

程序访问的局部性

程序访问的局部性:在较短时间间隔,程序产生的访存地址往往集中一个很小的范围,这种现象称为程序访问的局部性

时间局部性:被访问的存储单元在较短时间内很可能被重复访问

空间局部性:被访问的存储单元的临近单元在较短时间内很可能被访问

例题

假定一个数组元素按行优先存放,以下两段伪代码程序段 A 和 B 中:

  1. 对于数组 a 的访问,哪一个空间局部性更好?哪一个时间局部性更好

    对于数组 A 按行访问在空间局部性更好,因为每次访问的都是临近的单元,因为是累加,不会在短时间重复访问同一个元素所以数组 A 和数组 B 在时间局部性上都很差

  2. 变量 sum 的空间局部性和时间局部性各如何?

    空间局部性对于单个变量是没有意义的所以无论程序段 A 还是 B 在空间局部性上都没有意义

    对于 sum 变量来说都是定义后立马在程序中访问,所以程序段 A 和程序段 B 的 sum 变量的时间局部性是一样的

  3. 对于指令访问来说,for 循环体的空间局部性和时间局部性如何?

    循环体内中的指令是连续存放在内存中的,所以在程序段 A 和 程序段 B 中的空间局部性是一样的都很好

    两段代码中都是两层循环,程序段 A 的sum + 指令执行次数为 m×nm \times n 而程序段 B sum + 指令的执行次数也是m×nm \times n 结果是一样的,所以时间局部性是一样的

    // 程序段 A
    
    int sum_array_rows(int a[M][N]) {
      int i,j,sum=0;
      for(i = 0; i < M; i++) {
        for(j = 0; j < N; j++) {
          sum += a[i][j];
        }
      }
      return sum;
    }
    
    // 程序段 B
    
    int sum_array_cols(int a[M][N]) {
      int i,j,sum = 0;
      for(j = 0; j < N; j++) {
        for(i = 0; i < M; i++) {
          sum += a[i][j];
        }
      }
      return sum;
    }
    

装入/存储指令的操作过程

对于指令load r0 , 6#指令操作过程

将主存地址为 6 的内容送到 r0 寄存器

image.png

image.png

  1. CPU 将主存地址 6 送到主存,主存控制器将 6送至 DRAM 芯片
  2. 主存将地址 6 中的数据 x 通过数据线送到主存控制器,再由主存控制器将数据送到 CPU 的总线接口部件中
  3. CPU 从总线接口中取出 x 存到寄存器 r0 中

对指令store 10# , r0 存数操作的大致过程

将寄存器 r0 的内容存到主存地址为 10 的内存单元中

image.png

image.png

  1. CPU 把地址 10 送到主存
  2. CPU 将主存地址为 10 的内容 y 送到数据线
  3. 主存将 数据 y 存入 10 号内存单元

Cache 工作原理

Cahce 是一种小容量高速缓冲存储器,由快速的 SRAM(静态 RAM)存储元组成,直接集成在 CPU 芯片内,速度几乎与 CPU 一样快

命中:在访存过程中需要判断所访问信息是否在 Cache 中。弱 CPU 访问单元所在的块在 Cache 中,则称 Cache 命中,命中概率称为命中率pp,它等于命中次数与访问总次数之比

缺失:若不在 Cache 中则为不命中缺失,其概率为缺失率,它等于不命中次数与访问总次数之比

行(槽):为方便 Cache 与主存交换信息,一般将 Cache 和主存空间划分为大小相等的区域,主存中的区域称为,也称主存块,它是 Cache 和主存之间信息交换单位;Cache 中存放一个主存块的区域称为

Cache的有效位:在系统启动或复位时,每个 Cache 行都为空,其中的信息无效,只有装入了主存块后信息才有效。为了说明 Cache 行中的信息是否有效,每个 Cache 行需要一个有效位。有了有效位就可以通过有效位清 0 来淘汰某 Cache 行中的主存块,称为冲刷,装入一个新主存块时,再将有效位置 1

CPU 在 Cache 的访问过程

image.png 整个访问过程如下:判断信息是否在 Cache,若是,则直接从 Cache 取信息;若否,则从主存取一个主存块到 Cache,如果对应 Cache 行已满,则需要替换 Cache 中的信息,因此,Cache 中的内容是主存中部分内容的副本

Cache 主存层次的平均访问时间

CPU 在 Cache 中直接存取信息,所用时间即为 Cache 访问时间 Tc,称为命中时间;缺失时需要从主存读取一个主存块送入 Cache,并同时将所需信息送入 CPU,因此所用时间为主存访问时间 Tm 和 Cache 访问时间Tc之和通常将TM称为缺失损失

CPU在 Cache 到主存层次的平均访问时间Ta=p×Tc+(Tm+Tc)×(1p)=Tc+(1p)×TmT_a = p \times T_c + (T_m + T_c) \times (1-p) = T_c + (1-p) \times T_m

Cache命中率×Cache命中时间+(主存访问时间+Cache命中时间)×未命中率Cache 命中率 \times Cache命中时间 + (主存访问时间 + Cache 命中时间) \times 未命中率

例题

设 CPU 时钟周期为 2ns,某程序有 3000 条指令,每条指令执行一次,其中 4 条指令在取指时发生 cache 缺失,其余命中。在执行指令过程中,该程序需要 1000 次主存数据访问,其中 6 次发生 cache 缺失,问

  1. 执行该程序的 cache 命中率是多少

    命中率=命中次数总访问次数=4000(取指令4次缺失+主存访问6次缺失)取指令3000+主存访问1000=39904000=0.9975=99.75%命中率 = \frac{命中次数}{总访问次数} = \frac{4000 - (取指令{\kern 5pt}4{\kern 5pt}次缺失 + 主存访问{\kern 5pt}6{\kern 5pt}次缺失)}{取指令{\kern 5pt}3000{\kern 5pt}次 + 主存访问{\kern 5pt}1000{\kern 5pt}次} = \frac{3990}{4000} = 0.9975 = 99.75\%

  2. 若 cache 命中时间为 1 个时钟周期,缺失为 10 个时钟周期,则 CPU 平均访问时间是多少

    Tc=2nsT_c = 2ns Tm=2ns×10=20nsT_m = 2ns \times 10 = 20ns

    CPU平价访问时间 = Tc+(1p)×Tm=2+0.0025×20=2.05nsT_c + (1-p) \times T_m = 2 + 0.0025 \times 20 = 2.05ns

Cache 行和主存块的映射方式

根据不同的映射规则,主存块和 cache 行之间有以下三种映射方式

  1. 直接映射:每个主存块映射到 cache 的固定行中

    直接映射的基本思想是将一个主存块映射到固定的 cache 行中,也称模映射,其映射关系

    cache 行号 = 主存块号 mod cache 行数

    直接映射的优点是容易实现,命中时间短,但由于多个主存块会映射到同一个 cache 行,当访问集中在这些主存块时,就会引起频繁的调进调出,即使其他 cache 行都空闲,也无法充分利用。

  2. 全相联:每个主存块映射到 cache 的任意行中

    全相联的基本思想是主存块可装入任意 cache 行中。因此,全相联 cache 需要比较所有 cache 行标记才能判断是否命中,同时,主存地址中无需 cache 行索引,只有标记和块内地址两个字段。全相联方式下,只要有空闲 cache 行,就不会发生冲突,因而块冲突概率低

    为判断是否命中,通常为每个 cache 行分别设置一个比较器,其位数等于标记字段的位数。全相联 cache 访存时根据标记字段的内容来查找 cache 行,因而是一种按内容访问方式,相应电路是一种相联存储器,其时间开销和所用元件开销都较大,因此全相联方式不适合容量较大的 cache

  3. 组相联:每个主存块映射到 cache 的固定组的任意行中

    组相联方式结合了直接映射和全相联的优点。当 cache 组数为 1 时,变为全相联模式;当每组只有一个 cache 行时,则变为直接映射。组相联的冲突概率比直接映射低,由于只有组内隔行采用全相联映射,所以比较器的位数和个数都比全相联少,易于实现,查找速度也快得多

直接映射例题

设 Cache 采用直接映射,主存块大小 64B,按字节编址。Cache 数据区大小为 1KB,主存空间大小为 256KB 问:

  1. 主存地址如何划分?用图表表示主存块和 Cache 行的映射关系

image.png

计算主存块号:首先用主存大小乘 1024 转换单位为 B,除以 64,得到将主存一共划分为多少块

256×102464=4096\frac{256 \times 1024}{64} = 4096

要计算 4096 需要使用多少位 2 进制来表示,要确定一个整数需要多少位二进制来表示,可以使用对数运算公式为 log2(n+1)log_2{(n + 1)}

log2(4096+1)=log2409712log_2{(4096 + 1)} = log_2{4097} \approx 12

共需要 12 位二进制表示,根据上图主存地址部分可以看出主存块号正好需要 12 位二进制

计算块内地址:主存储器中某一块里面的内容,一块大小 64B,按字节编址一块中有 64 字节,共需要log264=6log_2{64} = 6位来表示,正好与主存地址的块内地址相符

接下来计算 cache 的行号,已知 cache 每一行的大小与内存块大小一致,cache 的大小为 1KB,每一块主存的大小为 64B

1024÷64=161024 \div 64 = 16 也就是说 cache 共 16 行

log216=4log_2{16} = 4 16 需要使用 4 位二进制来表示,得到了 cache 行号

上面已知主存块号共 12 位,由标记与 cache 行号组成,经过计算 cache 行号为 4 位二进制表示,那么标记则为 124=812 - 4 = 8 位二进制

  1. 假设 Cache 为空,说明 CPU 对主存单元 0240CH 的访问过程

    (0240C)16 = (9228)10

    12×160+0×161+4×162+2×16312 \times 16^0 + 0 \times 16^1 + 4 \times 16^2 + 2 \times 16^3

    那么得到实际想访问的地址为 9228

    内存地址是连续的,每一块内存区域为 64B

    9228÷64=1449228 \div 64 = 144 得到该存储单元在内存 144 块

    第二种计算方式

    将 (0240C)16 = (0000 0010 01|0000|00 1100)2 因为主存地址共 18 位,该地址共 20 位需要删除前两的两个 0

    该地址的前 12 位为主存块号 转换为 10 进制为 144 与上面的计算结果相同

    根据题目 Cache 为空,访问 0240CH 单元的过程如下:

    ​ 首先根据地址中间 4 位 cache 行号 0000,找不到 cache 第 0 行,因为 cache 当前为空,所以,每个 cache 行的有效位都为 0,因此不管第 0 行的标志是否等于 00 0010 01,都不命中。此时,将 0240CH 单元所在的主存第 144 块复制到 cache 第 0 行,并置有效位为 1,置标记为 00 0010 01(表示信息取自主存第 9 块群)

全相联映射例题

设 Cache 采用全相联方式,主存块大小 64B,按字节编址。Cache 数据区大小为 1KB,主存空间大小为 256KB 问:

  1. 主存地址如何划分?用图表表示主存块和 Cache 行的映射关系

image.png

首先计算主存块号,首先用主存大小乘以 1024 将单位转换为 B 除以 主存块大小 算出主存一共有多少块

256×102464=4096\frac{256 \times 1024}{64} = 4096

计算 4096 需要多少位二进制来表示,要确定一个整数需要多少位二进制表示需要使用对数公式log2n+1log_2{n+1}

log2(4096+1)=log2409712log_2{(4096 + 1)} = log_2{4097} \approx 12

共需 12 位二进制表示,可以看出全相联映射方式下主存块号为 12 位二进制

计算快内地址:主存块大小为 64B 64 需要使用 log264=6log_2{64} = 6 位二进制表示,根据上图可以看出块内地址为 6 位二进制

  1. 说明 CPU 对主存单元 0240CH 的访问过程

    (0240C)16 = (9228)10

    12×160+0×161+4×162+2×16312 \times 16^0 + 0 \times 16^1 + 4 \times 16^2 + 2 \times 16^3

    那么得到实际想访问的地址为 9228

    内存地址是连续的,每一块内存区域为 64B

    9228÷64=1449228 \div 64 = 144 得到该存储单元在内存 144 块

    第二种计算方式

    将 (0240C)16 = (0000 0010 0100 00|00 1100)2 因为主存地址共 18 位,该地址共 20 位需要删除前两的两个 0

    该地址的前 12 位为主存块号 转换为 10 进制为 144 与上面的计算结果相同

    访问 0240CH 单元的过程如下

    ​ 首先将高 12 位标记 00 0010 0100 00 与每个 cache 行标记进行比较,若有一个相等且有效位为 1,则命中,此时,CPU 根据块内地址 00 1100 从该行中取出信息;若都不相等,则不命中,此时,需要将 0240CH 单元所在主存第 00 0010 0100 00 块(即第 144 块)读出,并装入任意一个空闲 cache 行中,置有效位为 1,置标记为 00 0010 0100 00(表示信息取自主存第 144 块)

  2. 设 Cache 采用 2 路组相联映射,主存块大小 64B,按字节编址。Cache 数据区大小为 1KB,主存空间大小为 256KB 问:

    1. 主存地址如何划分?画图表示主存块和 Cache 行的映射关系

image.png

首先计算主存块号,首先用主存大小乘以 1024 将单位转换为 B 除以 主存块大小 算出主存一共有多少块

256×102464=4096\frac{256 \times 1024}{64} = 4096

计算 Cache 中有多少块 主存块大小 64B,Cache 大小1KB

102464=16\frac{1024}{64} = 16

计算Cache 有多少组 由于是采用两路组相联,即每两块 Cache 为一组

16÷2=816 \div 2 = 8

根据上图可知 cache 组号为 3 位二进制表示,log28=3log_2{8} = 3

计算将内存分为多少组 内存共 4096 块,Cache 共 8 组

4095÷8=5124095 \div 8 = 512

内存分为 512 个组群,每个组群中有 8 块,每个组群的第 0 块则对应 Cache 中的第 0 组,第 1 块对应 Cache 的第 1 组

  1. 说明 CPU 对主存单元 0240CH 的访问过程

    (0240C)16 = (9228)10

    12×160+0×161+4×162+2×16312 \times 16^0 + 0 \times 16^1 + 4 \times 16^2 + 2 \times 16^3

    那么得到实际想访问的地址为 9228

    内存地址是连续的,每一块内存区域为 64B

    9228÷64=1449228 \div 64 = 144 得到该存储单元在内存 144 块

    第二种计算方式

    将 (0240C)16 = (0000 0010 010|000|00 1100)2 因为主存地址共 18 位,该地址共 20 位需要删除前两的两个 0

    该地址的前 12 位为主存块号 转换为 10 进制为 144 与上面的计算结果相同

    访问0240CH 单元的过程如下

    ​ 首先根据地址中间 3 位 Cahce 组号 000,找到 Cache 第 0 组,将标记 00 0010 010 与第 0 组中两个 Cache 行的标记进行比较,若有一个相等且有效位为 1,则命中。此时,根据低六位块内地址从对应行中取出单元内容送 CPU;若都不相等或有一个相等但有效位为 0,则不命中。此时,将 0240CH 单元所在的主存第 0 0001 0010 000块(即第 144 块)复制到 Cache 第 0 组任意一个空行中,并置有效位为 1,置标记为 0 0001 0010(表示信息取自第 18 组群

Cache 中主存块的替换算法

  1. 先进先出算法基本思想(FIFO):总是选择最早装入 Cache 的主存块被替换掉。这种算法实现方便,但不能正确反映程序的访问局部性,因为最先进入的主存块也可能是目前经常要用的,因此这种算法有可能产生较大的缺失率
  2. 最近少用算法基本思想(LRU):总是选择近期最少使用的主存块被替换掉。这种算法能比较正确地反映程序的访问局部性,因为当前最少使用的块一般来说也是将来最少被访问的。但是,它的实现比先进先出算法要复杂。LRU 算法用计数值来记录主存块的使用情况,通过硬件修改计数值,并根据计数值选择淘汰某个 Cache 行中的主存块。这个计数值称为 LRU 位,其位数与 cache 组的大小有关,两路时占 1 位,4 路时占 2 位,8 路时占 3 位。为降低 LRU 计数器的硬件实现成本,通常采用伪 LRU算法。
    1. 伪 LRU 算法的思想:仅记录 cache 组内每个主存块的近似使用情况,以区分哪些是新装入的主存块,哪些是较长时间未用的主存块,替换时在那些较长时间未用的主存块中选择一个换出
  3. 最不常用算法基本思想(LFU):替换掉 Cache 中引用次数最少的块。LFU也用与每个行相关的计数器来实现。这种算法与 LRU 有点类似,但不完全相同
  4. 随机替换算法基本思想:从候选行的主存块中随机选取一个淘汰掉,与使用情况无关。模拟实验表明,随机替换算法在性能上只稍逊于基于使用情况的算法,而且代价低

例题

设 Cache 共 16 行,开始为空,主存块大小 1 个字,采用直接映射,按字节编址。CPU 执行某程序时,依次访问以下地址序列:2、3、11、16、21、13、64、48、19、11、3、22、4、27、6、11问:

  1. 上述地址序列的命中率是多少

    cache 行号 = 主存块号 mod cache 行数

    主存块大小可以计算出地址在主存的第几块,例如主存块大小为 1,那么主存块与地址就是一一对应的

    地址231116211364481911322427611
    主存块231116211364481911322427611
    Cache231105130031136411611

    由于 Cache 开始为空,下面开始模拟访问过程

    1. 访问 2:缓存第 2 行 为空,未命中,将主存块号为 2 的内容放入缓存第 2
    2. 访问 3:缓存第 3 行为空,未命中,将主存块号为 3 的内容放入缓存第 3
    3. 访问 11:缓存第 11 行为空,未命中,将主存块号为 11 的内容放入缓存第 11
    4. 访问 16:缓存第 16 行为空,未命中,将主存块号为 16 的内容放入缓存第 16
    5. 访问 21:主存块号 21 对应的缓存行号为 5,第 5 行为空,未命中,将主存块号为 21 的内容放入缓存第 5
    6. 访问 13:缓存第 13 行为空,未命中,将第 13 块主存加入缓存第 13
    7. 访问 64:主存块号 64 对应的缓存行号为 0 此时缓存第 0 行有内容存放的是主存第 16 块的内容所以未命中,使用主存块第 64 块内容覆盖第 16 块内容
    8. 访问 48:主存块号 48 对应的缓存行号为 0 此时缓存第 0 行有内容存放的是主存第 64 块的内容所以未命中,使用主存块第 48 块内容覆盖第 64 块内容
    9. 访问 19:主存块号 19 对应的缓存行号为 3 此时缓存第 3 行有内容存放 的是主存第 3 块的内容所以未命中,使用主存块第 19 块内容覆盖第 3 块内容
    10. 访问 11:缓存行第 11 行不为空,存放的是主存块第 11 块内容,命中
    11. 访问 3:缓存行第 3 行不为空存放的是主存块第 19 块所以未命中,使用主存块第 3 块内容覆盖第 19 块内容
    12. 访问 22:主存块号 22 对应的缓存行号为 6,第 6 行为空,未命中,将主存块号为 21 的内容放入缓存第 6
    13. 访问 4:缓存第 4 行 为空,未命中,将主存块号为 4 的内容放入缓存第 4
    14. 访问 27:主存块号 27 对应的缓存行号为 11 此时缓存第 11 行有内容存放的是主存第 11 块的内容所以未命中,使用主存块第 27 块内容覆盖第 11 块内容
    15. 访问 6:缓存行第 6 行不为空存放的是主存块第 22 块所以未命中,使用主存块第 6 块内容覆盖第 22 块内容
    16. 访问 11:缓存行第 11行不为空存放的是主存块第 27 块所以未命中,使用主存块第 11 块内容覆盖第 27 块内容

    上面共访问 16次,命中一次所以命中率为 116\frac{1}{16}

  2. 若 Cache 数据区容量不变,而块大小改为 4 个字,则上述地址序列命中情况又如何

    主存块大小与 Cache 大小是一致的,上一题中 Cache 为 16 行,每个主存块大小 1 个字,现在块变大四倍,那么 Cache 行数则要缩小 4 倍变为 4 行

    主存块修改为 4 个字,相当于将主存每 4 块分为一组,使用地址 除以 4 可以得到该地址在主存的第几组中

    地址231116211364481911322427611
    主存块002453161242051612
    Cache0020130002011212

    由于 Cache 开始为空,下面开始模拟访问过程

    1. 访问 2:缓存第 0 行 为空,未命中,将主存块号为 0 的内容放入缓存第 0
    2. 访问 3:缓存行第 0 行不为空,存放的是主存块第 0 块内容,命中
    3. 访问 11:缓存第 2 行为空,未命中,将主存块号为 2 的内容放入缓存第 2
    4. 访问 16:缓存行第 0 行不为空存放的是主存块第 0 块所以未命中,使用主存块第 4 块内容覆盖第 0 块内容
    5. 访问 21:主存块号 21 对应的缓存行号为 1,第 1 行为空,未命中,将主存块号为 21 的内容放入缓存第 1
    6. 访问 13:缓存第 3 行 为空,未命中,将主存块号为 3 的内容放入缓存第 3
    7. 访问 64:缓存行第 0 行不为空存放的是主存块第 4 块所以未命中,使用主存块第 16 块内容覆盖第 4 块内容
    8. 访问 48:缓存行第 0 行不为空存放的是主存块第 16 块所以未命中,使用主存块第 12 块内容覆盖第 16 块内容
    9. 访问 19:缓存行第 0 行不为空存放的是主存块第 12 块所以未命中,使用主存块第 4 块内容覆盖第 12 块内容
    10. 访问 11:缓存行第 2 行不为空,存放的是主存块第 2 块内容,命中
    11. 访问 3:缓存行第 0 行不为空存放的是主存块第 4 块所以未命中,使用主存块第 0 块内容覆盖第 4 块内容
    12. 访问 22:缓存行第 5 行不为空,存放的是主存块第 5 块内容,命中
    13. 访问 4:缓存行第 1 行不为空存放的是主存块第 5 块所以未命中,使用主存块第 1 块内容覆盖第 5 块内容
    14. 访问 27:缓存行第 2 行不为空存放的是主存块第 2 块所以未命中,使用主存块第 6 块内容覆盖第 2 块内容
    15. 访问 6:缓存行第 1 行不为空,存放的是主存块第 1 块内容,命中
    16. 访问 11:缓存行第 2 行不为空存放的是主存块第 6 块所以未命中,使用主存块第 2 块内容覆盖第 6 块内容

    上面共访问 16次,命中三次所以命中率为 416=14=25%\frac{4}{16} = \frac{1}{4} = 25\%

    上一问中命中率为116\frac{1}{16} 提高块大小后命中率变为416\frac{4}{16}所以增加块大小有助于提升命中率

    Cache 的写策略

    1. 通写法(全写法、直写法或写直达法):若写命中,则同时写 Cache 和主存,以保持两者一致;若写缺失,则先写主存,并有以下两种处理方式
      1. 写分配法:分配一个 Cache 行并装入更新后的主存块。这种方式可以充分利用空间局部性,但每次写缺失都要装入主存块,因此增加了写缺失的处理开销
      2. 非写分配法:不将主存块装入 Cache。这种方式可以减少写缺失的处理时间,但没有充分利用空间局部性
    2. 回写法(一次性写、写回法):若写命中,则只将内容写入 Cache 而不写入主存,弱写缺失,则分配一个 Cache 行并装入主存块,然后更新该行的内容。因此,回写法通常与写分配法组合使用

    例题

    主存空间大小 256M,按字节编址。指令 Cache 和数据 Cache 分离,两种 Cache 均有 8 个 Cache 行,主存与 Cache 交换的块大小为 64B,数据 Cache 采用2 路组相联、通写法和 LRU 替换算法。两个功能相同的代码 A 和 B 如下代码所示。假定i、j、sum均分配在寄存器中,数组 a 按行优先方式存放,首地址为 320,问:

    // 程序 A
    int a[256][256];
    ……
    int sum_array1 () {
      int i,j,sum = 0;
      for(i = 0; i < 256; i++) {
        for(j = 0; j < 256; j++) {
          sum += a[i][j];
        }
      }
      return sum;
    }
    
    // 程序 B
    int a[256][256];
    ……
    int sum_array2 () {
      int i,j,sum = 0;
      for(j = 0; j < 256; j++) {
        for(i = 0; i < 256; i++) {
          sum += a[i][j];
        }
      }
      return sum;
    }
    
    1. 数据 Cache 的总容量(包括标记和有效位等)为多少?

      确定主存的位数256×1024×1024=228b256 \times 1024 \times 1024 = 2^{28}b 也就是说主存地址的位数为 28 位二进制

      块内地址:交换块大小为 64b,log264=6log_2{64} = 6位 需要用 6 位二进制表示

      已只主存地址的组成为 标记 + Cache 组号 + 块内地址 块内地址已只为 6 位,

      Cache 组号:已只 Cache 有 8 行,采用两路组相联,即每组两行,共分为 4 行,log24=2log_2{4} = 2 可以知道 组号为 2 位二进制

      标记主存地址=28Cache组号块内地址=2826=20主存地址 = 28 - Cache 组号 - 块内地址 = 28 - 2 - 6 = 20

      Cache 行的组成:标记 + 有效位(0/1)占 1 位 + 替换算法占 1 位 + 数据(题目提供 64B,需要转换为 b 所以需要乘 8)

      Cache 的总容量

      64×8+20+1+1=534b64 \times 8 + 20 + 1 + 1 = 534b 得到一行 Cache 的容量

      534×8=4272b534 \times 8 = 4272b

      ​ 因为使用字节编址所以4272需要除以 8 4272÷8=534B4272 \div 8 = 534B

    2. 数组元素a[0][30]和a[1][16]各自所在主存块对应的 Cache 组号分别是多少

      数组是 int 型数组,是 32 位的 因为是字节编址所以转化为 4B

      计算地址公式(×数组行数+)×类型+首地址(行 \times 数组行数 + 列) \times 类型 + 首地址

      a[0][30]:(0×256+30)×4+320=440(0 \times 256 + 30) \times 4 + 320 = 440

      a[1][16]:(1×256+16)×4+320=1408(1 \times 256 + 16) \times 4 + 320 = 1408

      计算所在块

      公式数组地址÷Cache大小\lfloor 数组地址 \div Cache 大小 \rfloor 表示向下取整

      ​ a[0][30]:440÷64=6\lfloor 440 \div 64 \rfloor = 6

      ​ a[1][16]:1408÷64=22\lfloor 1408 \div 64 \rfloor = 22

      计算 Cache 组号

      ​ 因为共有 8 行 Cache 采用两路组相联,所以共四组

      ​ a[0][30]:6mod4=2 6 {\kern 5pt} mod {\kern 5pt}4 = 2

      ​ a[1][16]:1408mod4=2 1408{\kern 5pt} mod {\kern 5pt} 4 = 2

    3. 程序 A 和程序 B 数据访问命中率各是多少?哪个程序执行时间更短?

      从每一块来看

      命中率=命中次数总访问次数命中率 = \frac{命中次数}{总访问次数}

      ​ 由于 i、j、sum 均在寄存器中,所以不涉及内存访问

      ​ a 数组所占主存块 = 256×256×464=4096\frac{256 \times 256 \times 4}{64} = 4096

      ​ 每个 Cache 可以放多少个元素 644=16\frac{64}{4} = 16

      ​ a 数组起始块 32064=5\frac{320}{64} = 5 也就是说从第五块主存块一直存到第 4096 块主存块

      ​ 访问的第一块内存中可以存放 16 个元素,也就是说 a[0][0]第 1 次访问是未命中的,但是第 2 次到第 16 次全都是命中的,根据规律可以得到 后面 4096 个内存块的命中情况与第一块的命中情况是一致的

      ​ 所以命中率是 1516=93.75%\frac{15}{16} = 93.75 \%

      从访存次数和它所占的字节来看

      命中率=命中次数总访问次数命中率 = \frac{命中次数}{总访问次数}

      ​ 由于 i、j、sum 均在寄存器中,所以不涉及内存访问

      ​ a 数组所占主存块 = 256×256×464=4096\frac{256 \times 256 \times 4}{64} = 4096

      ​ 总访问次数:256×256=65536256 \times 256 = 65536

      ​ 根据上面的结果,每一块的第一次都是未命中,剩下的都是命中

      ​ 即未命中次数为 4096

      65536409665536=93.75\frac{65536 - 4096}{65536} = 93.75

      程序 B 的命中率

      ​ 由于程序 B 是按列优先访问,依次访问的两个数组元素分布在相隔 256×4=1024256 \times 4 = 1024 的单元处,即a[i][j]和a[i+1][j]之间的存储地址相差 1024B,即 16 块,因为 16mod4=016 {\kern 5pt} mod 4 {\kern 5pt} = 0,因此,它们映射到同一个 Cache 组,即第 j 列的 256 次内循环所访问的所有数组元素所在主存块(共 256 个主存块)都映射到了同一个 Cache 组,而每个 Cache 组有两行,因此共替换了256÷2=128256 \div 2 = 128 次,每次总是后面访问的两个主存块依次替换掉前面的两个主存块,使得每次都不能命中,命中率为 0

      因为程序 A 的命中率比程序 B 的要高,所以程序 A 的执行时间比程序 B 的执行时间短

捏捏捏捏捏捏捏捏捏捏捏

由于后续视频课程为付费课程,观看方式为萝卜 Bro 发送的腾讯会议链接无法分享给大家,所以还是建议大家去购买正版课程 😊😊