cache 订阅
Caché,是 一种后关系型数据库。能并发访问同一数据的数据库技术。 展开全文
Caché,是 一种后关系型数据库。能并发访问同一数据的数据库技术。
信息
定    义
一种后关系型数据库
作    用
对事务数据的实时查询
针对对象
为专业开发者而设计
中文名
Caché
Caché简介
什么是 Caché?。它独一无二地提供了三种整合的、能并发访问同一数据的数据库技术:成熟的对象访问方式,高性能的 SQL 访问方式以及丰富的多维访问。在对象数据、关系型数据以及多维数据视图之间不需要映射,这样就大大节省了开发和运行时间。
收起全文
精华内容
下载资源
问答
  • Cache模拟器

    热门讨论 2014-05-26 18:54:44
    程序使用C/C++混合编程,基本实现的Cache的模拟功能(通过读取trace文件得到相应的命中率),能够实现直接映射、全相联、组相联三种映射方式,其中全相联和组相联能够实现随机、LRU两种替换策略。目前三种映射方式均...
  • 该工程包含数据缓存D_Cache和指令缓存I_Cache的Verilog代码和仿真文件,Cache的详细技术参数包含在.v文件的注释中。 直接相连16KB D_Cache Cache写策略: 写回法+写分配 (二路)组相连16KB I_Cache Cache替换策略: ...
  • Cache数据库学习资料

    2015-08-27 09:54:52
    资料里面包括Cache+面向对象软件开发教程.pdf、Caché数据库管理和维护手册.pdf、M语言等
  • CPU的cache工作原理

    千次阅读 2021-04-05 14:43:52
    CPU的cache工作原理 博主微信:flm13724054952,不懂的有疑惑的也可以加微信咨询,欢迎大家前来指教共同探讨,谢谢!博主最近的工作是CPU集成设计,所以接下来的篇章将以CPU的学习讲解为主。最后再打个小广告,欢迎...

    CPU的cache工作原理

    博主微信:flm13724054952,不懂的有疑惑的也可以加微信咨询,欢迎大家前来指教共同探讨,谢谢!博主最近的工作是CPU集成设计,所以接下来的篇章将以CPU的学习讲解为主。最后再打个小广告,欢迎各位对数字IC设计感兴趣的博友来我们公司“众星微”,可以内推哦。

    1 about Cache

    在思考CPU的架构为什么需要cache之前,我们首先来了解一下:CPU是如何运行软件程序的。
    我们应该知道程序是运行在 RAM之中,RAM 就是我们常说的DDR(L3: DDR、Flash等)。我们称之为main memory(主存)。当我们需要运行一个进程的时候,首先会从磁盘设备(L4,CF card、UFS、SSD等)中将可执行程序load到主存中,然后开始执行。在CPU内部存在一堆的通用寄存器(register)。 如果CPU需要将一个变量(假设地址是A)加1,一般分为以下3个步骤:(在没有cache的情况下)
    (1) CPU 从主存中读取地址A的数据到内部通用寄存器 R0;(一个cycle)
    (2) 通用寄存器 R0 加1;(一个cycle)
    (3) CPU 将通用寄存器 x0 的值写入主存。(几十个cycle)
    CPU系统里面cache与memory的层次结构流程图会如下图所示, 如果有cache作为主存与CPU之间的缓存数据地带,一方面变量不需要频繁写到主存先缓存在cache里面,那么指令的执行时间将大大加快。另一方面CPU可以不用长时间占用总线的访问。Cache是存在于主存与CPU之间的一级存储器, 由静态存储芯片(SRAM)组成,容量比较小但速度比主存高得多, 接近于CPU的速度。 Cache的功能是用来存放那些近期需要运行的指令与数据。目的是提高CPU对存储器的访问速度。 当CPU试图从主存中load/store数据的时候, CPU会首先从cache中查找对应地址的数据是否缓存在cache 中。如果其数据缓存在cache中,直接从cache中拿到数据并返回给CPU。
    在这里插入图片描述

    2 cache architecture

    在传统的CPU计算机架构里面,cache架构目前分为两大架构诺依曼架构跟哈维架构。在诺依曼架构里面,一个cache同时用于指令与数据,是统一的一个Cache。在哈维架构里面,指令与数据总线是分开的,所以同时出现两个Cache,I-Cache跟D-Cache。在arm的CPU系统里面,会有不同的指令与数据一级缓存,由统一的L2-Cache支持。
    如下图所示,在Cache的架构里面,I-Cache由data_ram跟tag_ram组成;D-Cache由data_ram,tag_ram,dirty_ram组成。
    Cache line:CPU一次性从主缓存搬移数据位宽的能力,如R5的cache line为32byte;
    Cache tag:指示cache的数据对应来自主缓存的哪个位置,同个cache line的tag是一样的;
    Cache index:指示将主缓存的数据编译到Cache的哪一行,决定了cache的size大小;
    Cache way:将Cache均分成几块MEM来存储,每一块都称为ways;
    Cache hit:CPU在cache寻找数据时,如果tag与主存tag一致那就是缓存命中;
    Cache miss:CPU在cache寻找数据时,如果tag与主存tag不一致那就是缓存缺失;
    在这里插入图片描述data ram
    在Cache的架构里面,data_ram就是对软件呈现的缓存数据的地方。
    tag ram
    在Cache的架构里面,tag_ram是告诉cache(data_ram)的数据是来自与主缓存的对应位置。tag_ram里面还有一个valid bit来指示当前对应的cache_line的数据是否是有效的。
    dirty ram
    在Cache的架构里面,dirty ram仅仅是D-Cache所拥有,其作用是在指示当前D-Cache的数据是否与主缓存一致,一致的话dirty为1,不一致的话dirty为0。
    cache的容量
    cache的容量计算如下:
    (1)用cache line跟ways来计算容量:例如R5的cache line为32个字节,D-Cache有4ways,(一个ways由两个数据位宽32bits 数据深度1024lines的bank拼接在一起);所以cache容量占据32×4×1024×2÷8=32KB;
    (2)用下图数据位宽,深度,数据块数来计算:所以cache容量占据32×1024×8÷8=32KB;
    在这里插入图片描述一个cache line一般还含有ag字段和valid位,通常情况下我们都是用cache中数据部分占的空间表示cache的容量,也就是32字节,但是实际上,它还额外多占用了tag ram跟dirty ram的存储空间。

    3 Cache maping

    Cache的映射原则是根据cache index来定位主缓存的数据应该搬移到cache的哪一行,然后把对应物理地址的tag信息及地址对应数据放入对应的tag ram跟data ram。并把相关的valid位置1。后面当CPU要在cache寻找数据的时候就可以根据cache line的tag字段来辨识当前的cache line数据是那个block的。
    如下图所示,Cache的映射方式有三种,直接映射(direct-mapping),组相连映射(set-associative-mapping),全相连映射(full-associative-mapping)。
    在这里插入图片描述

    3.1 直接映射(direct-mapping)

    直接映射缓存是cache只有一个ways,搬移数据直接就在同一块上缓存;所以在硬件设计上会更加简单,因此成本上也会较低。但是也会引入其他问题,如下图所示,根据直接映射缓存的工作方式,我们可以看到,地址0x00-0x3f地址处对应的数据可以覆盖整个cache。0x40-0x7f地址的数据也同样是覆盖整个cache。
    在这里插入图片描述我们现在思考一个问题,如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?首先我们应该明白0x00、0x40、0x80地址中index部分是一样的。因此,这3个地址对应的cache line是同一个。所以,当我们访问0x00地址时,cache会缺失,然后数据会从主存中加载到cache中第0行cache line。当我们访问0x40地址时,依然索引到cache中第0行cache line,由于此时cache line中存储的是地址0x00地址对应的数据,所以此时依然会cache缺失。然后从主存中加载0x40地址数据到第一行cache line中。同理,继续访问0x80地址,依然会cache缺失。这就相当于每次访问数据都要从主存中读取,所以cache的存在并没有对性能有什么提升。访问0x40地址时,就会把0x00地址缓存的数据替换。这种现象叫做cache颠簸(cache thrashing)。针对这个问题,我们引入多路组相连缓存。我们首先研究下最简单的两路组相连缓存的工作原理。

    3.2 组相连映射(set-associative-mapping)

    两路组相连缓存的硬件成本相对于直接映射缓存更高。因为其每次比较tag的时候需要比较多个cache line对应的tag(某些硬件可能还会做并行比较,增加比较速度,这就增加了硬件设计复杂度)。为什么我们还需要两路组相连缓存呢?因为其可以有助于降低cache颠簸可能性。那么是如何降低的呢?
    如下图所示:根据两路组相连缓存的工作方式,两路组相连缓存较直接映射缓存最大的差异就是:第一个地址对应的数据可以对应2个cache line,而直接映射缓存一个地址只对应一个cache line。那么这究竟有什么好处呢?
    在这里插入图片描述我们依然考虑直接映射缓存一节的问题“如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?”。现在0x00地址的数据可以被加载到way 1,0x40可以被加载到way 0。这样是不是就在一定程度上避免了直接映射缓存的尴尬境地呢?在两路组相连缓存的情况下,0x00和0x40地址的数据都缓存在cache中。试想一下,如果我们是4路组相连缓存,后面继续访问0x80,也可能被被缓存。
    因此,当cache size一定的情况下,组相连缓存对性能的提升最差情况下也和直接映射缓存一样,在大部分情况下组相连缓存效果比直接映射缓存好。同时,其降低了cache颠簸的频率。从某种程度上来说,直接映射缓存是组相连缓存的一种特殊情况,每个组只有一个cache line而已。因此,直接映射缓存也可以称作单路组相连缓存。

    3.3 全相连映射(full-associative-mapping)

    既然组相连缓存那么好,如果所有的cache line都在一个组内。岂不是性能更好。其实并没有完美的cache-mapping,例如全相连缓存是将所有的cache line都放置在一个组内,主缓存的任何数据可以放在任何一个cache line里面。虽然这可以最大程度的降低cache颠簸的频率。但是硬件(设计的复杂性)成本上也是更高。所以设计这样子的一个全相连的Cache是不切合实际的,除非这个cache非常之小。实际在应用上来说对于L1 Cache来说当超过4 ways以上的性能的提升已经是最小的了。
    如下图所示CPU最常用的组相连映射方案:对于一个4ways 32KB的D-Cache,cache line是8字。那么1 ways就会有256 lines。当cache line=256时,那么cache index=8;另外还需要3bit去指示cache line中哪一个字是有效的。
    在这里插入图片描述

    4 Cache controller

    缓存控制器是一个管理维护cache的硬件逻辑电路,对软件程序而言是不可见的。他可以自动把数据从主缓存写到cache里面,还可以响应来自CPU core的读写请求并执行相应的操作去Cache或者主缓存。
    当CPU core发起读请求的时候,他会检测这个地址是否在cache里面。这个行为叫做查表(cache look-up)。然后通过检验cache line的tag是否与查找地址tag一致来决定cache是否命中。当cache hit而且cache line是有效的,那么读操作将在cache发生。
    当CPU core请求的指令或者数据来自一个特殊的地址(对应的tag不在cache里面),那么就会发生缓存丢失(cache miss)。当发生了cache miss那么这个请求将会出去外面的主缓存响应(要么L2 Cache要么external memory)。他也会导致发生了cache linefill,及在请求主缓存的相应数据到CPU core去时,也复制一份到cache里面来。但是这些行为对于你来说是不可见的。CPU core在使用数据之前不必等待行填充完成。缓存控制器通常会首先访问缓存线中的关键字。例如,如果一个load指令在缓存中缺失并触发了一个cache linefill,那么第一次读到外部内存就是load指令提供的实际地址。这些关键数据被提供给处理器管道,而高速缓存硬件和外部总线接口然后在后台读取高速缓存线的其余部分。

    5 Cache policies

    cache policies里面有三种策略allocate policy,replacement policy,write policy来影响cache的操作。

    5.1 allocate policy

    read allocate policy
    只在读取时分配cache线路。如果内核执行了缓存中未执行的写操作,那么缓存不会受到影响,写操作将进入层次结构的下一层。
    write allocate policy
    读或写在cache中失败的情况分配一条cache line。因此,更准确地说,这可以称为读写缓存分配策略。对于缓存中未执行的内存读操作和缓存中未执行的内存写操作,都会执行缓存行填充操作。这通常与当前ARM核心上的回写策略结合使用。

    5.2 replacement policy

    replacement policy是在当cache发生cache miss的时候,缓存控制器需要将数据从主缓存搬移到cache的策略。当数据从主缓存搬移到cache而选择一个cahce line的行为叫做victim(牺牲)。但是选择cache line的某一行是不固定随机的,如果选择的那一行包含了有用的数据而被我们替换掉新数据的话,那么会对后续访问造成影响,那么就需要把选中的这一行cache line数据搬移到主缓存保留,这种行为就叫做逐出(eviction)。
    就如上面所说,replacement policy就是如何选择cache line来做victim。大部分的处理器支持两种替换策略Round-robin or cyclic replacement policy跟Pseudo-random replacement policy。
    1、Round-robin or cyclic replacement policy
    就是一个victim-counter,在一个有效的cache ways里面做周期循环计数,当计数超过ways的最大行数就归0,重新计数,这样子来选择victim cache line。
    2、Pseudo-random replacement policy
    就是一个伪随机替换Pseudo-random-victim counter,通过伪随机函数来随机选择cache的某一行去做victim cache line。
    总的来说 循环替代政策通常更具可预测性,但某些用例中的性能可能会受到不利影响。因此,伪随机策略通常是首选。

    5.3 write policy

    write policy的策略分为了两种策略write-throughwrite-back
    1、CPU向cache写入数据时的操作,两者的区别:
    Write-through:CPU向cache写入数据时,同时向memory(后端存储)也写一份,使cache和memory的数据保持一致;
    Write-back:CPU更新cache时,只是把更新的cache区标记一下,并不同步更新memory(后端存储)。只是在cache区要被新进入的数据取代时,才更新memory(后端存储)。
    2、两者相比较优势跟缺点:
    Write-through:优点是简单,但是每次都要访问memory,速度比较慢;
    Write-back:优点是CPU执行的效率提高,缺点是实现起来技术比较复杂。
    系统设计人员应评估最适合其应用程序的缓存操作策略要求。如果使用了回写策略,那么缓存通常必须在在切换上下文以保持内存系统的一致性之前描述的。这个切换前可能需要大量写入CP15寄存器。或者,选择直写策略可以降低系统性能并增加功耗每个缓存操作都保持一致性,这有时是不必要的。然而,这意味着缓存正在不断地被清理,因此缓存维护通常是花更少的时间。

    6 write and fetch buffer

    write buffer是一个位于CPU内部的硬件逻辑块。当CPU执行一条store指令,数据往往会先写到writer buffer中去,然后CPU不等他真正写到主缓存就去执行下一条指令 。但是writer buffer过一段时间会主动把数据写到主缓存之中去。
    因此writer buffer的优点就是可以大大提供CPU执行指令的能力,提高整体性能,但是当CPU大量往writerbuffer写数据且速度比write buffer写到主缓存的速度快,那么writer buffer很快就会被写满,那么这个时候对CPU的性能提升就不是很明显了。而且当软件同事需要CPU想获取当前的数据再去执行下一条指令的时候,那么这个时候writer buffer反而也是碍事了。
    一些write buffer还有一个写合并的功能,对相连的单发写数据会合并在一起,通过brust的方式写到主缓存去,这样子可以提高数据流的传输速度。但是相反来说,当软件想要的是单字节操作数据不是连续一段数据,那么这个时候就是反而碍事了。
    在某些系统中,称为fetch buffer的类似组件可以用于读取。特别是,CPU core通常包含预取缓冲区,在指令实际插入到管道之前从内存中读取指令。通常,这样的缓冲区对你来说是透明的。在研究内存排序规则时,我们也应该考虑与此相关的一些可能的危险。

    参考文献

    <<arm-v7架构手册>>

    展开全文
  • Cache的基本原理

    千次阅读 多人点赞 2020-09-07 23:02:45
    对于没有接触过底层技术的朋友来说,或许从未听说过cache。毕竟cache的存在对程序员来说是透明的。在接触cache之前,先为你准备段code分析: int arr[10][128]; for (i = 0; i < 10; i++) for (j = 0; j <...

    对于没有接触过底层技术的朋友来说,或许从未听说过cache。毕竟cache的存在对程序员来说是透明的。在接触cache之前,先为你准备段code分析:

    int arr[10][128];
    
    for (i = 0; i < 10; i++)
            for (j = 0; j < 128; j++)
                    arr[i][j] = 1;

    如果你曾经学习过C/C++语言,这段code自然不会陌生。如此简单的将arr数组所有元素置1。 你有没有想过这段code还有下面的一种写法:

    int arr[10][128];
    
    for (i = 0; i < 128; i++)
            for (j = 0; j < 10; j++)
                    arr[j][i] = 1;

    功能完全一样,但是我们一直在重复着第一种写法(或许很多的书中也是建议这么编码),你是否想过这其中的缘由?文章的主角是cache,所以你一定猜到了答案。那么cache是如何影响这2段code的呢?

    为什么需要cache

     

    在思考为什么需要cache之前,我们首先先来思考另一个问题:我们的程序是如何运行起来的?

    我们应该知道程序是运行在 RAM之中,RAM 就是我们常说的DDR(例如: DDR3、DDR4等)。我们称之为main memory(主存)。当我们需要运行一个进程的时候,首先会从磁盘设备(例如,eMMC、UFS、SSD等)中将可执行程序load到主存中,然后开始执行。在CPU内部存在一堆的通用寄存器(register)。如果CPU需要将一个变量(假设地址是A)加1,一般分为以下3个步骤:

    1. CPU 从主存中读取地址A的数据到内部通用寄存器 x0(ARM64架构的通用寄存器之一)
    2. 通用寄存器 x0 加1
    3. CPU 将通用寄存器 x0 的值写入主存

    我们将这个过程可以表示如下:

    其实现实中,CPU通用寄存器的速度和主存之间存在着太大的差异。两者之间的速度大致如下关系:

    CPU register的速度一般小于1ns,主存的速度一般是65ns左右。速度差异近百倍。因此,上面举例的3个步骤中,步骤1和步骤3实际上速度很慢。当CPU试图从主存中load/store 操作时,由于主存的速度限制,CPU不得不等待这漫长的65ns时间。如果我们可以提升主存的速度,那么系统将会获得很大的性能提升。如今的DDR存储设备,动不动就是几个GB,容量很大。如果我们采用更快材料制作更快速度的主存,并且拥有几乎差不多的容量。其成本将会大幅度上升。我们试图提升主存的速度和容量,又期望其成本很低,这就有点难为人了。因此,我们有一种折中的方法,那就是制作一块速度极快但是容量极小的存储设备。那么其成本也不会太高。这块存储设备我们称之为cache memory。在硬件上,我们将cache放置在CPU和主存之间,作为主存数据的缓存。 当CPU试图从主存中load/store数据的时候, CPU会首先从cache中查找对应地址的数据是否缓存在cache 中。如果其数据缓存在cache中,直接从cache中拿到数据并返回给CPU。当存在cache的时候,以上程序如何运行的例子的流程将会变成如下:

    CPU和主存之间直接数据传输的方式转变成CPU和cache之间直接数据传输,cache负责和主存之间数据传输。

    多级cache存储结构

    cahe的速度在一定程度上同样影响着系统的性能。一般情况cache的速度可以达到1ns,几乎可以和CPU寄存器速度媲美。但是,这就满足人们对性能的追求了吗?并没有。当cache中没有缓存我们想要的数据的时候,依然需要漫长的等待从主存中load数据。为了进一步提升性能,引入多级cache。前面提到的cache,称之为L1 cache(第一级cache)。我们在L1 cache 后面连接L2 cache,在L2 cache 和主存之间连接L3 cache。等级越高,速度越慢,容量越大。但是速度相比较主存而言,依然很快。不同等级cache速度之间关系如下:

    经过3级cache的缓冲,各级cache和主存之间的速度最萌差也逐级减小。在一个真实的系统上,各级cache之间硬件上是如何关联的呢?我们看下Cortex-A53架构上各级cache之间的硬件抽象框图如下:

    在Cortex-A53架构上,L1 cache分为单独的instruction cache(ICache)和data cache(DCache),指令和数据分开。L1 cache是每个CPU私有的,每个CPU都有一个L1 cache。一个cluster 内的所有CPU共享一个L2 cache,L2 cache不区分指令和数据,都可以缓存。所有cluster之间共享L3 cache,L3 cache通过总线和主存相连

    多级cache之间的配合工作

    首先引入两个名词概念,命中和缺失。 CPU要访问的数据在cache中有缓存,称为“命中” (hit),反之则称为“缺失” (miss)。多级cache之间是如何配合工作的呢?我们假设现在考虑的系统只有两级cache:

    当CPU试图从某地址load数据时,首先从L1 cache中查询是否命中,如果命中则把数据返回给CPU。如果L1 cache缺失,则继续从L2 cache中查找。当L2 cache命中时,数据会返回给L1 cache以及CPU。如果L2 cache也缺失,很不幸,我们需要从主存中load数据,将数据返回给L2 cache、L1 cache及CPU。这种多级cache的工作方式称之为inclusive cache,也就是某一地址的数据可能存在多级缓存中。与inclusive cache对应的是exclusive cache,这种cache保证某一地址的数据缓存只会存在于多级cache其中一级。也就是说,任意地址的数据不可能同时在L1和L2 cache中缓存。

    直接映射缓存(Direct mapped cache)

    我们继续引入一些cache相关的名词。cache的大小称之为cahe size,代表cache可以缓存最大数据的大小。我们将cache平均分成相等的很多块,每一个块大小称之为cache line,其大小是cache line size。例如一个64 Bytes大小的cache。如果我们将64 Bytes平均分成64块,那么cache line就是1字节,总共64行cache line。如果我们将64 Bytes平均分成8块,那么cache line就是8字节,总共8行cache line。现在的硬件设计中,一般cache line的大小是4-128 Bytes。为什么没有1 byte呢?原因我们后面讨论。

    这里有一点需要注意,cache line是cache和主存之间数据传输的最小单位。什么意思呢?当CPU试图load一个字节数据的时候,如果cache缺失,那么cache控制器会从主存中一次性的load cache line大小的数据到cache中。例如,cache line大小是8字节。CPU即使读取一个byte,在cache缺失后,cache会从主存中load 8字节填充整个cache line。又是因为什么呢?后面说完就懂了。

    我们假设下面的讲解都是针对64 Bytes大小的cache,并且cache line大小是8字节。我们可以类似把这块cache想想成一个数组,数组总共8个元素,每个元素大小是8字节。就像下图这样:

    现在我们考虑一个问题,CPU从0x0654地址读取一个字节,cache控制器是如何判断数据是否在cache中命中呢?cache大小相对于主存来说,可谓是小巫见大巫。所以cache肯定是只能缓存主存中极小一部分数据。我们如何根据地址在有限大小的cache中查找数据呢?现在硬件采取的做法是对地址进行散列(可以理解成地址取模操作)。我们接下来看看是如何做到的?

    我们一共有8行cache line,cache line大小是8 Bytes。所以我们可以利用地址低3 bits(如上图地址蓝色部分)用来寻址8 bytes中某一字节,我们称这部分bit组合为offset。同理,8行cache line,为了覆盖所有行。我们需要3 bits(如上图地址黄色部分)查找某一行cache line,这部分地址部分称之为index

    现在我们知道,如果两个不同的地址,其地址的bit3-bit5如果完全一样的话,那么这两个地址经过硬件散列之后都会找到同一个cache line。所以,当我们找到cache line之后,只代表我们访问的地址对应的数据可能存在这个cache line中,但是也有可能是其他地址对应的数据,这是因为每一个cache line都可能对应不同的8byte地址,所以在寻址时为了区分每一个地址,我们又引入tag array区域,tag array和data array一一对应。每一个cache line都对应唯一一个tag,tag中保存的是整个地址位宽去除index和offset使用的bit剩余部分(如上图地址绿色部分)。tag、index和offset三者组合就可以唯一确定一个地址了

    因此,上面查找步骤主要分为:首先根据地址的bit3~bit5找到对应的cache line,然后比较地址中的高位tag值与该cache line对应的tag 值,如果相等则说明要找的地址在这个cache line中,然后根据后3位找到对应的byte,这就是cache命中;如果tag值不同,则是cache缺失。在上述图中,我们看到tag的值是0x19,和地址中的tag部分相等,因此在本次访问会命中。由于tag的引入,因此解答了我们之前的一个疑问“为什么硬件cache line不做成一个字节?”。这样会导致硬件成本的上升,因为原本8个字节对应一个tag,现在需要8个tag,占用了很多内存。

    我们可以从图中看到tag旁边还有一个valid bit这个bit用来表示cache line中数据是否有效(例如:1代表有效;0代表无效)。当系统刚启动时,cache中的数据都应该是无效的,因为还没有缓存任何数据。cache控制器可以根据valid bit确认当前cache line数据是否有效。所以,上述比较在tag确认cache line是否命中之前还会检查valid bit是否有效。只有在有效的情况下,比较tag才有意义。如果无效,直接判定cache缺失。

    上面的例子中,cache size是64 Bytes并且cache line size是8 bytes。offset、index和tag分别使用3 bits、3 bits和42 bits(假设地址宽度是48 bits)。我们现在再看一个例子:512 Bytes cache size,64 Bytes cache line size。根据之前的地址划分方法,offset、index和tag分别使用6 bits、3 bits和39 bits。如下图所示:

     

    直接映射缓存的优缺点

    直接映射缓存在硬件设计上会更加简单,因此成本上也会较低。根据直接映射缓存的工作方式,我们可以画出主存地址0x00-0x88地址对应的cache分布图:

    我们可以看到,地址0x00-0x3f地址处对应的数据可以覆盖整个cache。0x40-0x7f地址的数据也同样是覆盖整个cache。所以每一个cache line都与多个8byte 地址呈映射关系,这也解释了上面为什么要设置tag的原因。

    我们现在思考一个问题,如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?首先我们应该明白0x00、0x40、0x80地址中index部分是一样的。因此,这3个地址对应的cache line是同一个。所以,当我们第一次访问0x00地址时,cache会缺失,然后数据会从主存中加载到cache中第0行cache line。当我们访问0x40地址时,依然索引到cache中第0行cache line,由于此时cache line中存储的是地址0x00地址对应的数据,所以此时依然会cache缺失。然后从主存中加载0x40地址数据到第一行cache line中。同理,继续访问0x80地址,依然会cache缺失。这就相当于每次访问数据都要从主存中读取,所以cache的存在并没有对性能有什么提升。访问0x40地址时,就会把0x00地址缓存的数据替换。这种现象叫做cache颠簸(cache thrashing)。针对这个问题,我们引入多路组相连缓存。我们首先研究下最简单的两路组相连缓存的工作原理。

    两路组相连缓存(Two-way set associative cache)

    我们依然假设64 Bytes cache size,cache line size是8 Bytes。什么是路(way)的概念。我们将cache平均分成多份,每一份就是一路。因此,两路组相连缓存就是将cache平均分成2份,每份32 Bytes。如下图所示:

    cache被分成2路,每路包含4行cache line。我们将所有索引一样的cache line组合在一起称之为组。例如,上图中一个组有两个cache line,总共4个组。我们依然假设从地址0x0654地址读取一个字节数据。由于cache line size是8 Bytes,因此offset需要3 bits,这和之前直接映射缓存一样。不一样的地方是index,在两路组相连缓存中,index只需要2 bits,因为一路只有4行cache line。上面的例子根据index找到第2行cache line(从0开始计算),第2行对应2个cache line,分别对应way 0和way 1。因此index也可以称作set index(组索引)。先根据index找到set,然后将组内的所有cache line对应的tag取出来和地址中的tag部分对比,如果其中一个相等就意味着命中。

    因此,两路组相连缓存较直接映射缓存最大的差异就是:一个地址对应的数据可以对应2个cache line,而直接映射缓存一个地址只对应一个cache line。那么这究竟有什么好处呢?

    两路组相连缓存优缺点

    两路组相连缓存的硬件成本相对于直接映射缓存更高。因为每次比较tag的时候需要比较多个cache line对应的tag(某些硬件可能还会做并行比较,增加比较速度,这就增加了硬件设计复杂度)。为什么我们还需要两路组相连缓存呢?因为其可以有助于降低cache颠簸可能性。那么是如何降低的呢?根据两路组相连缓存的工作方式,我们可以画出主存地址0x00-0x4f地址对应的cache分布图:

    我们依然考虑直接映射缓存一节的问题“如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?”。现在0x00地址的数据可以被加载到way 1,0x40可以被加载到way 0。这样是不是就在一定程度上避免了直接映射缓存的尴尬境地呢?在两路组相连缓存的情况下,0x00和0x40地址的数据可以都缓存在cache中。试想一下,如果我们是4路组相连缓存,后面继续访问0x80,也可能被被缓存。

    因此,当cache size一定的情况下,组相连缓存对性能的提升最差情况下也和直接映射缓存一样,在大部分情况下组相连缓存效果比直接映射缓存好。同时,其降低了cache颠簸的频率。从某种程度上来说,直接映射缓存是组相连缓存的一种特殊情况,每个组只有一个cache line而已。因此,直接映射缓存也可以称作单路组相连缓存。

    全相连缓存(Full associative cache)

    既然组相连缓存那么好,如果所有的cache line都在一个组内,岂不是性能更好?是的,这种缓存就是全相连缓存。我们依然以64 Byts大小cache为例说明:

    由于所有的cache line都在一个组内,因此地址中不需要set index部分。因为,只有一个组让你选择,间接来说就是你没得选。我们根据地址中的tag部分和所有的cache line对应的tag进行比较(硬件上可能并行比较也可能串行比较)。哪个tag比较相等,就意味着命中某个cache line。因此,在全相连缓存中,任意地址的数据可以缓存在任意的cache line中。所以,这可以最大程度的降低cache颠簸的频率,但是硬件成本上也是更高。

    一个四路组相连缓存实例问题

    考虑这么一个问题,32 KB大小4路组相连cache,cache line大小是32 Bytes。请思考以下2个问题:

    1. 多少个组?
    2. 假设地址宽度是48 bits,index、offset以及tag分别占用几个bit?

    总共4路,因此每路大小是8 KB。cache line size是32 Bytes,因此一共有256组(8 KB / 32 Bytes)。由于cache line size是32 Bytes,所以offset需要5位。一共256组,所以index需要8位,剩下的就是tag部分,占用35位。这个cache可以绘制下图表示:

    Cache分配策略(Cache allocation policy)

    cache的分配策略是指我们什么情况下应该为数据分配cache line。cache分配策略分为读和写两种情况。

    读分配(read allocation)

    当CPU读数据时,发生cache缺失,这种情况下都会分配一个cache line缓存从主存读取的数据。默认情况下,cache都支持读分配。

    写分配(write allocation)

    当CPU写数据发生cache缺失时,才会考虑写分配策略。当我们不支持写分配的情况下,写指令只会更新主存数据,然后就结束了。当支持写分配的时候,我们首先从主存中加载数据到cache line中(相当于先做个读分配动作,将cache line 与地址相关联),然后会更新cache line中的数据。

    Cache更新策略(Cache update policy)

    cache更新策略是指当发生cache命中时,写操作应该如何更新数据。cache更新策略分成两种:写直通和回写

    写直通(write through)

    当CPU执行store指令并在cache命中时,我们更新cache中的数据并且更新主存中的数据。cache和主存的数据始终保持一致

    写回(write back)

    当CPU执行store指令并在cache中命中时,我们只更新cache中的数据。并且每个cache line中会有一个bit位记录数据是否被修改过,称之为dirty bit(翻翻前面的图片,cache line旁边有一个D就是dirty bit)。我们会将dirty bit置位。主存中的数据只会在cache line被替换或者显式的clean操作时更新,这样可以一下子向主存中写入多个cache line。因此,主存中的数据可能是未修改的数据,而修改的数据躺在cache中cache和主存的数据可能不一致。

    同时思考个问题,为什么cache line大小是cache控制器和主存之间数据传输的最小单位呢?这也是因为每个cache line只有一个dirty bit。这一个dirty bit代表着整个cache line是否被修改的状态。

    实例

    假设我们有一个64 Bytes大小直接映射缓存,cache line大小是8 Bytes,采用写分配和写回机制。当CPU从地址0x2a读取一个字节,cache中的数据将会如何变化呢?假设当前cache状态如下图所示(tag旁边valid一栏的数字1代表合法。0代表非法。后面Dirty的1代表dirty,0代表没有写过数据,即非dirty)。

    根据index找到对应的cache line,对应的tag部分valid bit是合法的,但是tag的值不相等,因此发生缺失。此时我们需要从地址0x28地址加载8字节数据到该cache line中。但是,我们发现当前cache line的dirty bit置位。因此,cache line里面的数据不能被简单的丢弃,由于采用写回机制,所以我们需要将cache中的数据0x11223344写到地址0x0128地址(这个地址根据tag中的值及所处的cache line行计算得到)。这个过程如下图所示:

    当写回操作完成,我们将主存中0x28地址开始的8个字节加载到该cache line中,并清除dirty bit。然后根据offset找到0x52返回给CPU。

     

    问题分析

    在有了以上背景假设后,我们先分析下最上面片段1导致的cache miss/hit情况。当执行arr[0][0] = 1时, cache控制器发现arr[0][0]的值不在cache中,此时发生一次cache miss。然后从主存中读取arr[0][0]到arr[0][15] 的内存值到cache中。当执行访问arr[0][1] = 1 时会发生一次cache hit(数组的存取按照行优先的原则进行)。此时内存访问速度极快。接着继续往下执行,会一直cache hit。直到执行arr[0][16] = 1,此时会cache miss。总结来说就是访问内存每发生一次cache miss。接下来会发生15次cache hit。因此这种初始化方法cache命中率很高

    我们再来分析下片段2。当执行arr[0][0] = 1时, cache控制器发现arr[0][0]的值不在cache中,此时发生一次cache miss。然后从主存中读取arr[0][0]arr[0][15] 的内存值到cache中。当执行访问arr[1][0] = 1 时依然发生一次cache miss。一直执行到arr[9][0] = 1依然是一次cache miss。现在思考下,访问arr[0][1]会是怎么情况呢? 此时就需要考虑cache的大小了。如果cache大小大于数组arr大小,cache此时相当于缓存了整个arr数组的内容。那么后续访问其他元素,确实是cache hit。似乎和片段1代码分析结果差不多。但是如果cache的大小很小,例如只有数组一半大小,那么cache命中率就很明显会降低。同样的cache大小,片段1的代码依然会获得很高的cache命中率。

    总结

    在大多数情况下,片段1代码的性能比片段2好。因此我们倾向片段1代码的写法。

    展开全文
  • cache主存映射一、三种映射方式1. 全相联映射2. 直接映射3. 组相联映射二、cache容量计算1. 先计算cache行标记项位数2. 再计算cache块位数3. 最后计算cache总容量三、cache写策略1. 写命中:(1)全写法 Write ...


    cache的工作原理:为了解决CPU和主存之间速度不匹配的问题,从而引入Cache,将某些主存块复制到Cache中。
    那么, 如何区分Cache与主存的数据块的对应关系呢?

    一、三种映射方式

    假设计算机主存大小256MB,按字节编址,数据cache有8行,行长64B。主存数据在cache里应该怎么存放?二者应该建立怎样的对应关系?
    【分析】行长即数据块大小,主存和cache是以数据块为单位进行数据交换的,因此主存块大小 ≡ \equiv cache块大小

    数据块大小64B(26B),以字节编址,占 6 位;
    主存大小256MB(228B),以字节编址,共占28位;

    采用不同的映射方式,会对主存地址(28位)有不同的划分方式:

    1. 全相联映射

    cache的所有行均可用于存放主存任何一块数据
    (助记:空位子都可以坐)

    缺点:查找最慢,当cache块装满的时候,可能需要遍历所有的cache行

    主存地址结构划分:

    主存块号块内地址
    22位6位

    查找策略:
    假如访问地址为:1…1101 001110

    1. 前22位依次与cache块标记进行对比
    2. 如果标记匹配且有效位为1,则cache命中,访问块内地址为001110的单元
    3. 若cache不命中或有效位为0,则正常访问内存

    2. 直接映射

    每个主存块只能放到某个固定的cache行,每个主存块所属的cahe行是 主存块号 % cache总行数
    (助记:一家公司员工每天都是不假思索直接去自己所属的公司)

    缺点:所以多个块会映射到同一行,这样会产生不必要的换入换出,因为即使cache有空行,也不能利用。
    (助记:公司有特定技能的人员需要,恰好当前团队没有这样的人才,但是刚好公司人员已经饱和了,那么就得有人先离开,新人再进来,这真是一个悲伤的故事)

    针对上例,「0号,8号,16号……」主存块被映射到0号cache行,「1号,9号,17号……」主存块被映射到1号cache行,以此类推……

    由于cache行总共8行(23),占 3 位;
    而恰好主存块号22位的后三位跟cache行号是一致的,所以将主存块号再拆分:

    主存地址结构划分:

    主存块号块内地址
    主存字块标记(19位) + cache行号(3位) = 22位6位

    查找策略:
    假如访问地址为:0…01000 001110

    1. 根据主存块号后3位确定cache行号
    2. 如果主存块号前19位与cache标记匹配且有效位为1,则cache命中,访问块内地址为001110的单元
    3. 若cache不命中或有效位为0,则正常访问内存

    3. 组相联映射

    首先要对所有的cache行进行分组,每个主存块只能放到固定的cache组,每个主存块所属的cahe组是 主存块号 % cache总组数

    (压轴出场的总是相对比较牛逼的,这种方式是上面两种方式的结合,主存数据先找到自己对应的组,组内的空位随便放,这样既提高了查找效率,又减少了因为冲突而导致的换入换出次数)

    仍然针对上例,以 2 路组相联为例,cache行可被划分为8/2=4组(22),占 2

    「0号,4号,8号……」主存块被映射到0号cache组,「1号,5号,9号……」主存块被映射到1号cache行,以此类推……

    而恰好主存块号22位的后 2 位跟cache组号是一致的,于是主存块号就有了另外一种划分方式:

    主存地址结构划分:

    主存块号块内地址
    主存字块标记(20位) + cache组号(2位) = 22位6位

    查找策略:
    假如访问地址为:1…0101 001110

    1. 根据主存块号后2位确定cache组号
    2. 如果主存块号前20位与cache分组内某行标记匹配且有效位为1,则cache命中,访问块内地址为001110的单元
    3. 若cache不命中或有效位为0,则正常访问内存

    三种映射方式附图:
    三种映射方式

    二、cache容量计算

    1. 先计算cache行标记项位数

    所有的cache行都会对应一个标记项,用于标记当前cache行保存的数据状态,cache行标记项结构如下:

    有效位标记位脏位替换控制位
    1bit主存字块标记1bit与替换算法有关

    * 有效位:(一定有)固定占 1 位,由于cache未装进数据块时,主存字块标记默认为0,所以有效位是为了区分当前cache块是没装数据还是装了一个主存第0的数据
    * 标记位:(一定有)主存字块标记位数,用于标识当前cache行存放的主存哪一行数据,计算方法见上
    脏位:(特定条件下才有)也叫一致性维护位,只有当cache写策略采用 写回法 时,该位生效并且占 1
    替换控制位: (特定条件下才有)或叫替换算法位,用于标记替换cache哪一行会被换出,在cache替换策略中,当采用LRU和LFU替换算法时,这个控制位会作为被换出的依据,见下图

    cache替换策略

    2. 再计算cache块位数

    题目中一般会以各种方式较为直观的给出,cache块大小和主存块大小是一致的,算出一个块所占据的位数
    数据位:由于主存块和cache块的交换是以 为单位,所以数据位即就是一个数据块的数据位数。

    3. 最后计算cache总容量

    根据cache总容量和cache块大小求得cache行数,最后

    c a c h e 总 容 量 = c a c h e 行 数 × ( c a c h e 行 标 记 项 位 数 + c a c h e 块 位 数 ) cache总容量=cache行数\times(cache行标记项位数+cache块位数) cache=cache×(cache+cache)

    【简例】已知4K字cache,块大小4个字,则:cache总1K行,cache行号占10位

    又已知主存块大小为4个字,每字32位,则数据块总位数 32*4=128位

    再根据块大小4个字,按字节编址,32*4/8=16 块内地址占4位

    主存地址32位,采用直接映射

    主存字块标记:32-10-4=18 为

    1K *(1+1+18+128)= 1K *(20+128)= 148K

    cache总容量 148K

    三、cache写策略

    由于cache 中的数据块是主存块的副本,当对 Cache 中的内容进行变更时,就要考虑主存如何变更以及在什么时机变更的问题~

    写操作也分为两种情况:
    写命中(Write Hit):要写的单元已经在 Cache 中
    写不命中(Write Miss):要写的单元不在 Cache 中

    在这里插入图片描述

    1. 写命中:

    (1)全写法 Write Through

    (又名写直达、直写法、写直通法)

    同时修改 cache 和主存。需要频繁写主存,访存次数增加,但能保证数据的一致性,应用于数据准确率要求高,实时性高的场景。一般使用写缓冲(write buffer)(SRAM实现的FIFO队列),在专门的控制电路控制下逐一写回。(若写操作很频繁,可能会因为写操作饱和而发生阻塞)

    (2)写回法 Write Back

    (又名写回、回写、一次性写)

    先修改 cache 暂不修改主存,当cache块要被换出时(根据脏位判断是否需要写回)一次性写主存。应用于写操作频繁,写密集型的场景;但存在数据不一致的隐患。

    2. 写不命中

    (1)写分配法 Write Allocate

    将主存块调入 cache 中,然后在Cache中修改,更新相应的地址单元。通常搭配写回法使用。这样可以利用空间局部特性,但是每次都要先读一个数据块到 cache 块,相对速度较慢

    (2)非写分配法 Not Write Allocate

    直接修改主存数据块里的地址单元,不把主存调入 cache 中

    通常,非写分配法和全写法搭配使用;写分配法和写回法搭配使用

    感谢你的认真阅读,如果你觉得这篇文章对你有用,欢迎点赞和加关注。
    如果你在计算机408的学习过程中还有难懂的问题,欢迎在评论区留言,我会在空闲时间挨个整理更新出来~

    展开全文
  • cache介绍

    千次阅读 2019-06-06 14:56:00
    原帖地址: http://www.wowotech.net/memory_management/458.html?from=timeline今天探究的主题是cache。... 为什么需要cache memory 在思考cache是什么之前我们首先先来思考第一个问题:我们的程...

    原帖地址: http://www.wowotech.net/memory_management/458.html?from=timeline


    今天探究的主题是cache。我们围绕几个问题展开。为什么需要cache?如何判断一个数据在cache中是否命中?cache的种类有哪些,区别是什么?

    为什么需要cache memory

          在思考cache是什么之前我们首先先来思考第一个问题:我们的程序是如何运行起来的?我们应该知道程序是运行在 RAM之中,RAM 就是我们常说的DDR(例如 DDR3、DDR4等)。我们称之为main memory(主存)当我们需要运行一个进程的时候,首先会从Flash设备(例如,eMMC、UFS等)中将可执行程序load到main memory中,然后开始执行。在CPU内部存在一堆的通用寄存器(register)。如果CPU需要将一个变量(假设地址是A)加1,一般分为以下3个步骤:

    1. CPU 从主存中读取地址A的数据到内部通用寄存器 x0(ARM64架构的通用寄存器之一)。
    2. 通用寄存器 x0 加1。
    3. CPU 将通用寄存器 x0 的值写入主存。

    我们将这个过程可以表示如下:

    image

          其实现实中,CPU通用寄存器的速度和主存之间存在着太大的差异。两者之间的速度大致如下关系:

    image

          CPU register的速度一般小于1ns,主存的速度一般是65ns左右。速度差异近百倍。因此,上面举例的3个步骤中,步骤1和步骤3实际上速度很慢。当CPU试图从主存中load/store 操作时,由于主存的速度限制,CPU不得不等待这漫长的65ns时间。如果我们可以提升主存的速度,那么系统将会获得很大的性能提升。如今的DDR存储设备,动不动就是几个GB,容量很大。如果我们采用更快材料制作更快速度的主存,并且拥有几乎差不多的容量。其成本将会大幅度上升。我们试图提升主存的速度和容量,又期望其成本很低,这就有点难为人了。因此,我们有一种折中的方法,那就是制作一块速度极快但是容量极小的存储设备。那么其成本也不会太高。这块存储设备我们称之为cache memory。在硬件上,我们将cache放置在CPU和主存之间,作为主存数据的缓存。 当CPU试图从主存中load/store数据的时候, CPU会首先从cache中查找对应地址的数据是否缓存在cache 中。如果其数据缓存在cache中,直接从cache中拿到数据并返回给CPU。当存在cache的时候,以上程序如何运行的例子的流程将会变成如下:

    image

    CPU和主存之间直接数据传输的方式转变成CPU和cache之间直接数据传输。cache负责和主存之间数据传输。

    多级cache memory

          cahe的速度在一定程度上同样影响着系统的性能。一般情况cache的速度可以达到1ns,几乎可以和CPU寄存器速度媲美。但是,这就满足人们对性能的追求了吗?并没有。当cache中没有缓存我们想要的数据的时候,依然需要漫长的等待从主存中load数据。为了进一步提升性能,引入多级cache。前面提到的cache,称之为L1 cache(第一级cache)。我们在L1 cache 后面连接L2 cache,在L2 cache 和主存之间连接L3 cache。等级越高,速度越慢,容量越大。但是速度相比较主存而言,依然很快。不同等级cache速度之间关系如下:

    image

          经过3级cache的缓冲,各级cache和主存之间的速度最萌差也逐级减小。在一个真实的系统上,各级cache之间硬件上是如何关联的呢?我们看下Cortex-A53架构上各级cache之间的硬件抽象框图如下:

    image

          在Cortex-A53架构上,L1 cache分为单独的instruction cache(ICache)和data cache(DCache)。L1 cache是CPU私有的,每个CPU都有一个L1 cache。一个cluster 内的所有CPU共享一个L2 cache,L2 cache不区分指令和数据,都可以缓存。所有cluster之间共享L3 cache。L3 cache通过总线和主存相连。

    多级cache之间的配合工作

          首先引入两个名词概念,命中和缺失。 CPU要访问的数据在cache中有缓存,称为“命中” (hit),反之则称为“缺失” (miss)。多级cache之间是如何配合工作的呢?我们假设现在考虑的系统只有两级cache。

    image

          当CPU试图从某地址load数据时,首先从L1 cache中查询是否命中,如果命中则把数据返回给CPU。如果L1 cache缺失,则继续从L2 cache中查找。当L2 cache命中时,数据会返回给L1 cache以及CPU。如果L2 cache也缺失,很不幸,我们需要从主存中load数据,将数据返回给L2 cache、L1 cache及CPU。这种多级cache的工作方式称之为inclusive cache。某一地址的数据可能存在多级缓存中。与inclusive cache对应的是exclusive cache,这种cache保证某一地址的数据缓存只会存在于多级cache其中一级。也就是说,任意地址的数据不可能同时在L1和L2 cache中缓存。

    直接映射缓存(Direct mapped cache)

          我们继续引入一些cache相关的名词。cache的大小称之为cahe size,代表cache可以缓存最大数据的大小。我们将cache平均分成相等的很多块,每一个块大小称之为cache line,其大小是cache line size。例如一个64 Bytes大小的cache。如果我们将64 Bytes平均分成64块,那么cache line就是1字节,总共64行cache line。如果我们将64 Bytes平均分成8块,那么cache line就是8字节,总共8行cache line。现在的硬件设计中,一般cache line的大小是4-128 Byts。为什么没有1 byte呢?原因我们后面讨论。

    这里有一点需要注意,cache line是cache和主存之间数据传输的最小单位。什么意思呢?当CPU试图load一个字节数据的时候,如果cache缺失,那么cache控制器会从主存中一次性的load cache line大小的数据到cache中。例如,cache line大小是8字节。CPU即使读取一个byte,在cache缺失后,cache会从主存中load 8字节填充整个cache line。又是因为什么呢?后面说完就懂了。

          我们假设下面的讲解都是针对64 Bytes大小的cache,并且cache line大小是8字节。我们可以类似把这块cache想想成一个数组,数组总共8个元素,每个元素大小是8字节。就像下图这样。

    image

          现在我们考虑一个问题,CPU从0x0654地址读取一个字节,cache控制器是如何判断数据是否在cache中命中呢?cache大小相对于主存来说,可谓是小巫见大巫。所以cache肯定是只能缓存主存中极小一部分数据。我们如何根据地址在有限大小的cache中查找数据呢?现在硬件采取的做法是对地址进行散列(可以理解成地址取模操作)。我们接下来看看是如何做到的?

    image

          我们一共有8行cache line,cache line大小是8 Bytes。所以我们可以利用地址低3 bits(如上图地址蓝色部分)用来寻址8 bytes中某一字节,我们称这部分bit组合为offset。同理,8行cache line,为了覆盖所有行。我们需要3 bits(如上图地址黄色部分)查找某一行,这部分地址部分称之为index。现在我们知道,如果两个不同的地址,其地址的bit3-bit5如果完全一样的话,那么这两个地址经过硬件散列之后都会找到同一个cache line。所以,当我们找到cache line之后,只代表我们访问的地址对应的数据可能存在这个cache line中,但是也有可能是其他地址对应的数据。所以,我们又引入tag array区域,tag array和data array一一对应。每一个cache line都对应唯一一个tag,tag中保存的是整个地址位宽去除index和offset使用的bit剩余部分(如上图地址绿色部分)。tag、index和offset三者组合就可以唯一确定一个地址了。因此,当我们根据地址中index位找到cache line后,取出当前cache line对应的tag,然后和地址中的tag进行比较,如果相等,这说明cache命中。如果不相等,说明当前cache line存储的是其他地址的数据,这就是cache缺失。在上述图中,我们看到tag的值是0x19,和地址中的tag部分相等,因此在本次访问会命中。由于tag的引入,因此解答了我们之前的一个疑问“为什么硬件cache line不做成一个字节?”。这样会导致硬件成本的上升,因为原本8个字节对应一个tag,现在需要8个tag,占用了很多内存。

    我们可以从图中看到tag旁边还有一个valid bit,这个bit用来表示cache line中数据是否有效(例如:1代表有效;0代表无效)。当系统刚启动时,cache中的数据都应该是无效的,因为还没有缓存任何数据。cache控制器可以根据valid bit确认当前cache line数据是否有效。所以,上述比较tag确认cache line是否命中之前还会检查valid bit是否有效。只有在有效的情况下,比较tag才有意义。如果无效,直接判定cache缺失。

    上面的例子中,cache size是64 Bytes并且cache line size是8 bytes。offset、index和tag分别使用3 bits、3 bits和42 bits(假设地址宽度是48 bits)。我们现在再看一个例子:512 Bytes cache size,64 Bytes cache line size。根据之前的地址划分方法,offset、index和tag分别使用6 bits、3 bits和39 bits。如下图所示。

    image

    直接映射缓存的优缺点

    直接映射缓存在硬件设计上会更加简单,因此成本上也会较低。根据直接映射缓存的工作方式,我们可以画出主存地址0x00-0x88地址对应的cache分布图。

    image

          我们可以看到,地址0x00-0x3f地址处对应的数据可以覆盖整个cache。0x40-0x7f地址的数据也同样是覆盖整个cache。我们现在思考一个问题,如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?首先我们应该明白0x00、0x40、0x80地址中index部分是一样的。因此,这3个地址对应的cache line是同一个。所以,当我们访问0x00地址时,cache会缺失,然后数据会从主存中加载到cache中第0行cache line。当我们访问0x40地址时,依然索引到cache中第0行cache line,由于此时cache line中存储的是地址0x00地址对应的数据,所以此时依然会cache缺失。然后从主存中加载0x40地址数据到第一行cache line中。同理,继续访问0x80地址,依然会cache缺失。这就相当于每次访问数据都要从主存中读取,所以cache的存在并没有对性能有什么提升。访问0x40地址时,就会把0x00地址缓存的数据替换。这种现象叫做cache颠簸(cache thrashing)。针对这个问题,我们引入多路组相连缓存。我们首先研究下最简单的两路组相连缓存的工作原理。

    两路组相连缓存(Two-way set associative cache)

          我们依然假设64 Bytes cache size,cache line size是8 Bytes。什么是路(way)的概念。我们将cache平均分成多份,每一份就是一路。因此,两路组相连缓存就是将cache平均分成2份,每份32 Bytes。如下图所示。

    image

          cache被分成2路,每路包含4行cache line。我们将所有索引一样的cache line组合在一起称之为组。例如,上图中一个组有两个cache line,总共4个组。我们依然假设从地址0x0654地址读取一个字节数据。由于cache line size是8 Bytes,因此offset需要3 bits,这和之前直接映射缓存一样。不一样的地方是index,在两路组相连缓存中,index只需要2 bits,因为一路只有4行cache line。上面的例子根据index找到第2行cache line(从0开始计算),第2行对应2个cache line,分别对应way 0和way 1。因此index也可以称作set index(组索引)。先根据index找到set,然后将组内的所有cache line对应的tag取出来和地址中的tag部分对比,如果其中一个相等就意味着命中。

    因此,两路组相连缓存较直接映射缓存最大的差异就是:第一个地址对应的数据可以对应2个cache line,而直接映射缓存一个地址只对应一个cache line。那么这究竟有什么好处呢?

    两路组相连缓存优缺点

          两路组相连缓存的硬件成本相对于直接映射缓存更高。因为其每次比较tag的时候需要比较多个cache line对应的tag(某些硬件可能还会做并行比较,增加比较速度,这就增加了硬件设计复杂度)。为什么我们还需要两路组相连缓存呢?因为其可以有助于降低cache颠簸可能性。那么是如何降低的呢?根据两路组相连缓存的工作方式,我们可以画出主存地址0x00-0x4f地址对应的cache分布图。

    image


          我们依然考虑直接映射缓存一节的问题“如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?”。现在0x00地址的数据可以被加载到way 1,0x40可以被加载到way 0。这样是不是就在一定程度上避免了直接映射缓存的尴尬境地呢?在两路组相连缓存的情况下,0x00和0x40地址的数据都缓存在cache中。试想一下,如果我们是4路组相连缓存,后面继续访问0x80,也可能被被缓存。

          因此,当cache size一定的情况下,组相连缓存对性能的提升最差情况下也和直接映射缓存一样,在大部分情况下组相连缓存效果比直接映射缓存好。同时,其降低了cache颠簸的频率。从某种程度上来说,直接映射缓存是组相连缓存的一种特殊情况,每个组只有一个cache line而已。因此,直接映射缓存也可以称作单路组相连缓存。

    全相连缓存(Full associative cache)

    既然组相连缓存那么好,如果所有的cache line都在一个组内。岂不是性能更好。是的,这种缓存就是全相连缓存。我们依然以64 Byts大小cache为例说明。

    image

          由于所有的cache line都在一个组内,因此地址中不需要set index部分。因为,只有一个组让你选择,间接来说就是你没得选。我们根据地址中的tag部分和所有的cache line对应的tag进行比较(硬件上可能并行比较也可能串行比较)。哪个tag比较相等,就意味着命中某个cache line。因此,在全相连缓存中,任意地址的数据可以缓存在任意的cache line中。所以,这可以最大程度的降低cache颠簸的频率。但是硬件成本上也是更高。

    一个四路组相连缓存实例问题

    考虑这么一个问题,32 KB大小4路组相连cache,cache line大小是32 Bytes。请思考一下问题:

    1). 多少个组? 2). 假设地址宽度是48 bits,index、offset以及tag分别占用几个bit?

    总共4路,因此每路大小是8 KB。cache line size是32 Bytes,因此一共有256组(8 KB / 32 Bytes)。由于cache line size是32 Bytes,所以offset需要5位。一共256组,所以index需要8位,剩下的就是tag部分,占用35位。这个cache可以绘制下图表示。

    image


    Cache分配策略(Cache allocation policy)

    cache的分配策略是指我们什么情况下应该为数据分配cache line。cache分配策略分为读和写两种情况。

    1. 读分配(read allocation):

      当CPU读数据时,发生cache缺失,这种情况下都会分配一个cache line缓存从主存读取的数据。默认情况下,cache都支持读分配。

    2. 写分配(write allocation):

      当CPU写数据发生cache缺失时,才会考虑写分配策略。当我们不支持写分配的情况下,写指令只会更新主存数据,然后就结束了。当支持写分配的时候,我们首先从主存中加载数据到cache line中(相当于先做个读分配动作),然后会更新cache line中的数据。

    Cache更新策略(Cache update policy)

    cache更新策略是指当发生cache命中时,写操作应该如何更新数据。cache更新策略分成两种:写直通和回写。

    1. 写直通(write through):

      当CPU执行store指令并在cache命中时,我们更新cache中的数据并且更新主存中的数据。cache和主存的数据始终保持一致。

    image

    2. 写回(write back):

           当CPU执行store指令并在cache命中时,我们只更新cache中的数据。并且每个cache line中会有一个bit位记录数据是否被修改过,称之为dirty bit(翻翻前面的图片,cache line旁边有一个D就是dirty bit)。我们会将dirty bit置位。主存中的数据只会在cache line被替换或者显示flush操作时更新。因此,主存中的数据可能是未修改的数据,而修改的数据躺在cache line中。

           同时,为什么cache line大小是cache控制器和主存之间数据传输的最小单位呢?这也是因为每个cache line只有一个dirty bit。这一个dirty bit代表着整个cache line时候被修改的状态。

    image

    实例 :

         假设我们有一个64 Bytes大小直接映射缓存,cache line大小是8 Bytes,采用写分配和写回机制。当CPU从地址0x2a读取一个字节,cache中的数据将会如何变化呢?假设当前cache状态如下图所示。


    image

          根据index找到对应的cache line,对应的tag部分valid bit是合法的,但是tag的值不相等,因此发生缺失。此时我们需要从地址0x28地址加载8字节数据到该cache line中。但是,我们发现当前cache line的dirty bit置位。因此,cache line里面的数据不能被简单的丢弃,由于采用写回机制,所以我们需要将cache中的数据0x11223344写到地址0x0128地址(这个地址根据tag中的值及所处的cache line行计算得到)。这个过程如下图所示。

    image

    当写回操作完成,我们将主存中0x28地址开始的8个字节加载到该cache line中,并清除dirty bit。然后根据offset找到0x52返回给CPU。

    cache替换策略

    一、最不经常使用算法(Least Frequently Used,LFU)

    LFU算法认为:应该将一段时间内被访问次数最少的那行数据替换出。

    实现方法:每行设置一个计数器。新行建立后从0开始计数,每被访问一次,被访问行的计数器增1。当需要替换时,对这些特定行的计数值进行比较,将计数值最小的行换出,同时将这些特定行的计数器清零。

    该算法不能严格反映近期访问情况。


    二、近期最少使用算法(Least Recently Used,LRU)

    LFU算法将近期内长久未被访问过的行换出。

    实现方法:每行设置一个计数器,但它们是cache每命中一次,命中行计数器清零,其他各行计数器增1。当需要替换时,将计数值最大的行换出。

    该算法保护了刚拷贝到cache中的新数据行,较为符合cache的工作原理,有效地提高了命中率。


    三、随机替换

    不需要算法,从特定的行位置中随机地选取一行换出即可。

    该策略在硬件上容易实现,速度较前两种策略快。但是会在一定程度上降低cache的效率。这些不足会随着cache容量增大而减小。

    转载于:https://www.cnblogs.com/mikewolf2002/p/10984976.html

    展开全文
  • 文章目录1、cache的应用——什么时候需要刷cache 1、cache的应用——什么时候需要刷cache (1)、cpu在往内存(src地址)写数据时,cache中会缓存这些数据,并没有立即同步到DDR, 只有该地址在cache中被换出去时候,才会...
  • 我们都知道,根据现代计算机存储介质的不同,我们引入了Cache 这个概念, Cache 在计算机芯片, 各级内存,硬盘,乃至于各种软件设计中都是非常常见的,Cache 使用的好,能够合理分层,我们能解决百分之八十以上的...
  • 高并发之——Guava Cache

    千次阅读 2019-10-27 11:18:02
    最近需要用到缓存来存放临时数据,又不想采用Redis,Java自带的Map功能太少,发现Google的Guava提供的Cache模块功能很强大,于是选择使用它。 本地缓存 本地缓存作用就是提高系统的运行速度,是一种空间换时间的...
  • Cache的基本原理以及简单操作

    千次阅读 2020-04-18 15:52:25
    对于没有接触过底层技术的朋友来说,或许从未听说过cache。毕竟cache的存在对程序员来说是透明的。在接触cache之前,先为你准备段code分析。 int arr[10][128]; for (i = 0; i < 10; i++) for (j = 0; j <...
  • SpringCache完整案例介绍

    万次阅读 多人点赞 2019-11-28 12:51:29
      Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager 接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;Cache接口为缓存的组件规范定义,...
  • Java Cache 入门

    千次阅读 2019-04-06 11:31:13
    什么是缓存 术语缓存在计算机中无处不在...hibernate.cache.default_cache_concurrency_strategy 来配置属性,配置选项如下: read-only :如果您的应用程序需要读取但不修改持久化类的实例,则只读缓存是最佳...
  • kube-scheduler的Cache解析

    万次阅读 2021-01-31 17:55:41
    前言 在阅读本文之前,建议从《kube-scheduler的SchedulingQueue解析》开始,因为笔者是以调度队列为...而本文分析的Cache中都是已经调度的Pod(更准确的说。。。),Cache是kube-scheduler的调度状态。 Cache //
  • Cache Memory简单介绍

    千次阅读 2019-08-16 10:46:07
    今天探究的主题是cache。我们围绕几个问题展开。为什么需要cache?如何判断一个数据在cache中是否命中?cache的种类有哪些,区别是什么? 为什么需要cache memory 在思考cache是什么之前我们首先先来思考第一个...
  • CPU Cache 机制以及 Cache miss

    千次阅读 2019-06-11 11:09:16
    CPU体系结构之cache小结 1.What is cache? Cache是用来对内存数据的缓存。 CPU要访问的数据在Cache中有缓存,称为“命中” (Hit),反之则称为“缺失” (Miss)。 CPU访问它的速度介于寄存器与内存之间(数量级的...
  • Cache

    千次阅读 2016-09-01 23:14:04
    Cache失效的原因 强制性失效 (Compulsory miss): 当第一次访问一个块时,该块不在 Cache中, 需从下一级存储中调入 Cache, 这就是强制性失效。这种失效也称为冷启动失效,或首次访问失效。(增加块大小,预取) 容量...
  • Cache地址映射

    万次阅读 多人点赞 2018-09-01 15:17:35
    理解Cache地址映射之前补充一些基础知识,Cache的地址映射和MMU(内存管理单元)和TLB Cache(转译查找缓存)中的映射是有区别的。Cache、TLB Cache、MMU在CPU中结构如图1所示,图1展现的是Cortex A9 Processor内部...
  • Django 缓存 Cache

    千次阅读 2018-12-25 17:35:43
    'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] }, # 我们也可以给缓存机器加权重,权重高的承担更多的请求,如下 ...
  • cache基础概念

    千次阅读 2018-09-10 07:19:26
    cache basic concept -v0.1 2018.9.8 Sherlock init -v1.0 2018.9.9 Sherlock finish in DongGuan 本文是学习cache基础知识过程中的一个笔记。学习的材料是《深入理解计算机系统》和 ARMv8的编程指导手册...
  • JetCache主要通过@Cached和@CreateCache实现缓存,@Cached是在接口方法或者类方法上添加缓存,一般以参数为key,以返回值为value存入缓存中。@CreateCache是直接创建一个缓存实例,然后调用put(Tkey, T value)、get...
  • Elasticsearch搜索引擎之缓存Request Cache: Request Cache 在每个分片上缓存了本地结果集,这使得频繁使用的搜索请求几乎立即返回结果。默认情况下只会缓存查询中参数 size=0 的搜索请求的结果,因此将不会缓存...
  • jetcache官网教程

    万次阅读 多人点赞 2018-05-09 22:58:57
    jetcache简介JetCache是一个基于Java的缓存系统封装,提供统一的API和注解来简化缓存的使用。 JetCache提供了比SpringCache更加强大的注解,可以原生的支持TTL、两级缓存、分布式自动刷新,还提供了Cache接口用于...
  • Cache组成及工作原理

    千次阅读 2018-12-12 15:17:40
        很多程序员认为Cache是透明的,处理器可以很聪明地安排他们书写的程序。他们非常幸运,可以安逸着忽略Cache,也安逸着被Cache忽略,日复一日,年复一年,机械地生产着各类代码。All of them are deceived。...
  • 缓存篇(二)- JetCache

    万次阅读 2018-09-28 19:27:25
    本文将由浅入深,从基本特性介绍,从简单demo使用,到JetCache源码分析,到Spring Aop的源码分析,到如何利用这些知识去自己尝试写一个自己的cache小demo,去做一个全面的概括。 *背景和特性 *用法demo *JetCache...
  • 前边文章主要介绍了下Caffeine以及CacheManager,这里说下Caffeine的其它...LoadingCache直接定义一个缓存,在项目中可以直接拿来存取数据,同时还可以引入CacheLoad加载数据。 LoadingCache是个接口,继承了Cache...
  • Guava系列之Cache

    千次阅读 多人点赞 2020-06-16 23:33:58
    本地Cache用法,了解一下
  • Cache超清晰逻辑详解(cache的三种映射)

    千次阅读 多人点赞 2020-11-08 23:50:28
    之前课上老师讲的飞快,课后复习压力很大,无意间看了他的tutorial感觉非常有收获,本篇博客基于tutorial,详细说明cache 我们知道,存储分层是为了在速度和存储容量上获得最优解,从cache,到主存到辅存,速度上...
  • 关于Texture Cache简单总结

    千次阅读 2019-02-22 19:15:51
    Texture Cache是一个存储图片数据的只读cache 按照正常uv顺序读贴图tex cache有高命中率 Texture Cache在 shader processor附近,所以它有高吞吐率,并且低延迟 上图可见有许多 shader core ...
  • 计算机缓存Cache以及Cache Line详解

    千次阅读 2019-04-11 16:06:11
    不管哪种映射,只要发生了cache miss,那么必定会有一个cacheline大小的内存区域,被取到cache中相应的cacheline。 现代处理器,一般将cache分为2~3级,L1, L2, L3。L1一般为CPU专有,不在多个CPU中共享。L2 cache...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,375,867
精华内容 550,346
关键字:

cache