关于c:深入理解计算机系统CSAPP读书笔记-第六章-存储器层次结构

  在计算机系统模型中,CPU执行指令,而存储器零碎为CPU寄存指令和数据。实际上,存储器零碎是一个具备不同容量、老本和拜访工夫的存储设备的层次结构
  如果你的程序须要的数据是存储在CPU寄存器中,那么在指令的执行期间,在0个周期内就能拜访到它们。如果存储在高速缓存中,须要4~75个周期。如果存储在主存中,须要上百个周期。而如果存储在磁盘上,须要大概几千万个周期!
  计算机程序的一个根本属性称为局部性。具备良好局部性的程序偏向于一次又一次地拜访雷同的数据项汇合,或是偏向于拜访邻近的数据项汇合。具备良好局部性的程序比局部性差的程序更多地偏向于从存储器层次结构中较高层次处拜访数据项,因而运行得更快。

[TOC]

存储技术

随机拜访存储器

  随机拜访存储器( Random-Access Memory,RAM)分为两类:动态的和动静的。动态RAM(SRAM)比动静RAM(DRAM)更快,但也贵得多。SRAM用来作为高速缓存存储器。DRAM用来作为主存以及图形系统的帧缓冲区

动态RAM

  SRAM将每个位存储在一个双稳态的( bistable)存储器单元里。每个单元是用一个六晶体管电路来实现的。这个电路有这样一个属性,它能够无限期地放弃在两个不同的电压配置( configuration)或状态( state)之一。其余任何状态都是不稳固的,在不稳固状态时,电路会迅速转移到两个稳固状态的一个。

  因为SRAM存储器单元的双稳态个性,只有有电,它就会永远地放弃它的值。即便有烦扰(例如电子乐音)来扰乱电压,当烦扰打消时,电路就会复原到稳固值。

动静RAM

  DRAM将每个位存储为对一个电容的充电。DRAM存储器能够制作得十分密集。每个单元由一个电容和一个拜访晶体管组成。然而,与SRAM不同,DRAM存储器单元对烦扰十分敏感。当电容的电压被扰乱之后,它就永远不会复原了。裸露在光线下会导致电容电压扭转。

  下表总结了SRAM和DRAM存储器的个性。只有有供电,SRAM就会放弃不变。与DRAM不同,它不须要刷新。SRAM的存取比DRAM快。SRAM对诸如光和电噪声这样的烦扰不敏感。代价是SRAM单元比DRAM单元应用更多的晶体管,因此密集度低,而且更贵,功耗更大。

每位晶体管数 绝对拜访工夫 继续的 敏感的 绝对破费 利用
SRAM 6 1X 1000X 高速缓存存储器
DRAM 1 10X 1X 主存,帧缓冲区

传统的DRAM

  DRAM芯片中的单元(位)被分成d个超单元( supercell),每个超单元都由w个DRAM单元组成。一个$d \times w$的DRAM总共存储了$dw$位信息。超单元被组织成一个r行c列的长方形阵列,这里rc=d。每个超单元无形如(i,j)的地址,这里i示意行,而j示意列。

  例如,如下图所示是一个16×8的DRAM芯片的组织,有d=16个超单元,每个超单元有w=8位,r=4行,c=4列。带暗影的方框示意地址(2,1)处的超单元。信息通过称为引脚(pin)的内部连接器流入和流出芯片。每个引脚携带一个1位的信号。下图给出了两组引脚:8个data引脚,它们能传送一个字节到芯片或从芯片传出一个字节,以及2个addr引脚,它们携带2位的行和列超单元地址。其余携带管制信息的引脚没有显示进去。

  每个DRAM芯片被连贯到某个称为内存控制器( memory controller)的电路,这个电路能够一次传送w位到每个DRAM芯片或一次从每个DRAM芯片传出w位。为了读出超单元(i,j)的内容,内存控制器将行地址i发送到DRAM,而后是列地址j。DRAM把超单元(i,j)的内容发回给控制器作为响应。行地址i称为RAS( Row Access strobe,行拜访选通脉冲)申请。列地址j称为CAS( Column Access strobe,列拜访选通脉冲)申请。留神,RAS和CAS申请共享雷同的DRAM地址引脚。

  例如,要从图6-3中16×8的DRAM中读出超单元(2,1),内存控制器发送行地址2,如下图a所示。DRAM的响应是将行2的整个内容都复制到一个外部行缓冲区。接下来,内存控制器发送列地址1,如下图b所示。DRAM的响应是从行缓冲区复制出超单元(2,1)中的8位,并把它们发送到内存控制器。

  电路设计者将DRAM组织成二维阵列而不是线性数组的一个起因是升高芯片上地址引脚的数量。例如,如果示例的128位DRAM被组织成一个16个超单元的线性数组,地址为0~15,那么芯片会须要4个地址引脚而不是2个。二维阵列组织的毛病是必须分两步发送地址,这减少了拜访工夫

加强的DRAM

  能够通过以下形式进步拜访根本DRAM的速度。

  快页模式DRAM( Fast Page Mode dram, FPM DRAM)。传统的DRAM将超单元的一整行复制到它的外部行缓冲区中,应用一个,而后抛弃残余的。FPM DRAM容许对同一行间断地拜访能够间接从行缓冲区失去服务

如果要读取第4行的3个超单元,传统DRAM须要收回3次RAS,CAS。而FPM DRAM只须要收回一次RAS,CAS,前面跟2个CAS即可。

  扩大数据输入DRAM( Extended Data Out Dram, EDO DRAM)。 FPM DRAM的个加强的模式,它容许各个CAS信号在工夫上靠得更严密一点。

 同步DRAM( Synchronous DRaM, SDRAM)。 SDRAM用与驱动内存控制器雷同的内部时钟信号的回升沿来代替许多这样的管制信号。最终成果就是 SDRAM可能比那些异步的存储器更快地输入它的超单元的内容。

  双倍数据速率同步DRAM( Double data- Rate SynchronouS DRAm, DDR SDRAM)。DDR SDRAM是对 SDRAM的一种加强,它通过应用两个时钟沿作为管制信号,从而使DRAM的速度翻倍。不同类型的 DDR SDRAM是用进步无效带宽的很小的预取缓冲区的大小来划分的:DDR(2位)、DDR2(4位)和DDR(8位)。

  视频RAM( Video ram,VRAM)。它用在图形系统的帧缓冲区中。VRAM的思维与 FPM DRAM相似。两个次要区别是:1)VRAM的输入是通过顺次对外部缓冲区的整个内容进行移位失去的;2)VRAM容许对内存并行地读和写。因而,零碎能够在写下一次更新的新值(写)的同时,用帧缓冲区中的像素刷屏幕(读)。

非易失性存储器

  如果断电,DRAM和SRAM会失落它们的信息,从这个意义上说,它们是易失的( volatile)。另一方面,非易失性存储器( nonvolatile memory)即便是在关电后,依然保留着它们的信息。

  对EPROM编程是通过应用一种把1写人 EPROM的非凡设施来实现的。 EPROM可能被擦除和重编程的次数的数量级能够达到1000次。EEPROM可能被编程的次数的数量级能够达到10次。

  闪存( flash memory)是一类非易失性存储器,基于 EEPROM,它曾经成为了一种重要的存储技术。

拜访主存

  数据流通过称为总线(bus)的共享电子电路在处理器和DRAM主存之间来来回回。每次CPU和主存之间的数据传送都是通过一系列步骤来实现的,这些步骤称为总线事务( bus transaction)。读事务( read transaction)从主存传送数据到CPU。写事务( write trans-action)从CPU传送数据到主存。

  总线是一组并行的导线,能携带地址、数据和管制信号。取决于总线的设计,数据和地址信号能够共享同一组导线,也能够应用不同的。同时,两个以上的设施也能共享同一总线。控制线携带的信号会同步事务,并标识出以后正在被执行的事务的类型。例如,以后关注的这个事务是到主存的吗?还是到诸如磁盘控制器这样的其余I/O设施?这个事务是读还是写?总线上的信息是地址还是数据项?

  展现了一个示例计算机系统的配置。次要部件是CPU芯片、咱们将称为IO桥接器(I/ O bridge)的芯片组(其中包含内存控制器),以及组成主存的DRAM内存模块这些部件由一对总线连接起来,其中一条总线是系统总线( system bus),它连贯CPU和I/O桥接器,另一条总线是内存总线( memory bus),它连贯I/O桥接器和主存。I/O桥接器将系统总线的电子信号翻译成内存总线的电子信号。

局部性

  一个编写良好的计算机程序经常具备良好的局部性( locality)。也就是,它们偏向于援用邻近于其余最近援用过的数据项的数据项,或者最近援用过的数据项自身。这种倾向性,被称为局部性原理( principle of locality),是一个长久的概念,对硬件和软件系统的设计和性能都有着极大的影响。局部性通常有两种不同的模式:工夫局部性( temporal locality)和空间局部性( spatial locality)。在一个具备良好工夫局部性的程序中,被援用过一次的内存地位很可能在不远的未来再被屡次援用。在一个具备良好空间局部性的程序中,如果一个内存地位被援用了次,那么程序很可能在不远的未来援用左近的一个内存地位。一般而言,有良好局部性的程序比局部性差的程序运行得更快。

  如下所示的函数sumvec,它对一个向量的元素求和。在这个例子中,变量sum在每次循环迭代中被援用一次,因而,对于sum来说,有好的工夫局部性。另一方面,因为sun是标量,对于sum来说,没有空间局部性。

int sumvec(int v[N])
{
    int i,sum = 0;
    for (i = 0; i < N; i++)
        sum += v[i];
    return sum;
}
援用模式:
地址:            0        4        8        12        16
内容:            v0        v1        v2        v3        v4
拜访程序:        1        2        3        4        5

  如上所示,向量v的元素是被程序读取的,一个接一个,依照它们存储在内存中的程序(为了不便,咱们假如数组是从地址0开始的)。因而,对于变量v,函数有很好的空间局部性,然而工夫局部性很差,因为每个向量元素只被拜访一次

步长为1的援用模式为程序援用模式( sequential reference pattern)。一个间断向量中,每隔k个元素进行拜访,就称为步长为k的援用模式( stride-k reference pattern)。步长为1的援用模式是程序中空间局部性常见和重要的起源。一般而言,随着步长的减少,空间局部性降落。

  如下的函数 sumarrayrows,它对一个二维数组的元素求和。双重嵌套循环依照行优先程序(row major order)读数组的元素。也就是,内层循环读第一行的元素,而后读第二行,依此类推。函数 sumarrayrows具备良好的空间局部性,因为它依照数组被存储的行优先程序来拜访这个数组。其后果是失去一个很好的步长为1的援用模式,具备良好的空间局部性。

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;
}
援用模式:
地址:            0        4        8        12        16
内容:            a00        a01        a02        a10        a11
拜访程序:        1        2        3        4        5

存储器层次结构

  存储技术和计算机软件的一些根本的和长久的属性:
  存储技术:不同存储技术的拜访工夫差别很大。速度较快的技术每字节的老本要比速度较慢的技术高,而且容量较小。CPU和主存之间的速度差距在增大。
  计算机软件:一个编写良好的程序偏向于展现出良好的局部性。

  硬件和软件的这些根本属性相互补充得很完满。它们这种互相补充的性质使人想到一种组织存储器零碎的办法,称为存储器层次结构( memory hierarchy),下图展现了一个典型的存储器层次结构。一般而言,从高层往底层走,存储设备变得更慢、更便宜和更大。在最高层(L0),是大量疾速的CPU寄存器,CPU能够在一个时钟周期内拜访它们。接下来是一个或多个小型到中型的基于SRAM的高速缓存存储器,能够在几个CPU时钟周期内拜访它们。而后是一个大的基于DRAM的主存,能够在几十到几百个时钟周期内拜访它们。接下来是慢速然而容量很大的本地磁盘。最初,有些零碎甚至包含了一层附加的近程服务器上的磁盘,要通过网络来拜访它们。

存储器构造中的缓存

  一般而言,高速缓存( cache,读作“cash”)是一个小而疾速的存储设备,它作为存储在更大、也更慢的设施中的数据对象的缓冲区域。应用高速缓存的过程称为缓存( caching,读作“ cashing”)。

  存储器层次结构的中心思想是,对于每个k,位于k层的更快更小的存储设备作为位于k+1层的更大更慢的存储设备的缓存。换句话说,层次结构中的每一层都缓存来自较低一层的数据对象。

  数据总是以块大小为传送单元( transfer unit)在第k层和第k+1层之间来回复制的。尽管在层次结构中任何一对相邻的档次之间块大小是固定的,然而其余的档次对之间能够有不同的块大小。如上图所示,L1和L0之间的传送通常应用的是1个字大小的块。L2和L1之间(以及L3和I2之间、I4和I3之间)的传送通常应用的是几十个字节的块。而L5和L4之间的传送用的是大小为几百或几千字节的块。一般而言,层次结构中较低层(离CPU较远)的设施的拜访工夫较长,因而为了弥补这些较长的拜访工夫,偏向于应用较大的块。

缓存命中

  当程序须要第k+1层的某个数据对象d时,它首先在以后存储在第k层的一个块中查找d。如果d刚好缓存在第k层中,那么就是咱们所说的缓存命中( cache hit)。

缓存不命中

  另一方面,如果第k层中没有缓存数据对象d,那么就是咱们所说的缓存不命中( cache miss)。当产生缓存不命中时,第k层的缓存从第k+1层缓存中取出蕴含d的那个块,如果第k层的缓存曾经满了,可能就会笼罩现存的一个块。(缓存的替换策略:随机替换替换策略,起码被应用(LRU)替换策略)。

缓存不命中品种

  辨别不同品种的缓存不命中有时候是很有帮忙的。如果第k层的缓存是空的,那么对任何数据对象的拜访都会不命中。一个空的缓存有时被称为冷缓存( cold cache),此类不命中称为强制性不命中( compulsory miss)或冷不命中( cold miss)。冷不命中很重要,因为它们通常是短暂的事件,不会在重复拜访存储器使得缓存暖身( warmed up)之后的稳固状态中呈现。

缓存治理

  存储器层次结构的实质是,每一层存储设备都是较低一层的缓存。在每一层上,某种模式的逻辑必须治理缓存。这里,咱们的意思是指某个货色要将缓存划分成块,在不同的层之间传送块,断定是命中还是不命中,并解决它们。治理缓存的逻辑能够是硬件、软件,或是两者的联合。

高速缓存存储器

  高速缓存对于读的操作非常简单。首先,在高速缓存中查找所需字$w$的正本。如果命中,立刻返回字$w$给CPU。如果不命中,从存储器层次结构中较低层中取出蕴含字$w$的块,将这个块存储到某个高速缓存行中(可能会驱赶一个无效的行),而后返回字$w$。

  写的状况就要简单一些了。假如咱们要写一个曾经缓存了的字$w$(写命中, write hit)。在高速缓存更新了它的$w$的正本之后,怎么更新$w$在层次结构中紧接着低一层中的正本呢?最简略的办法,称为直写( write-through),就是立刻将$w$的高速缓存块写回到紧接着的低一层中。尽管简略,然而直写的毛病是每次写都会引起总线流量。另一种办法,称为写回( write-back),尽可能地推延更新,只有当替换算法要驱赶这个更新过的块时,才把它写到紧接着的低一层中。因为局部性,写回能显著地缩小总线流量,然而它的毛病是减少了复杂性。高速缓存必须为每个高速缓存行保护一个额定的批改位( dirty bit),表明这个高速缓存块是否被批改过。

  另一个问题是如何解决写不命中。一种办法,称为写调配( write-allocate),加载相应的低一层中的块到高速缓存中,而后更新这个高速缓存块。写调配试图利用写的空间局部性,然而毛病是每次不命中都会导致一个块从低一层传送到高速缓存。另一种办法,称为非写调配(not- write-allocate),避开高速缓存,间接把这个字写到低一层中。直写高速缓存通常是非写调配的。写回高速缓存通常是写调配的。

  高速缓存既保留数据,也保留指令。只保留指令的高速缓存称为 i-cache。只保留程序数据的高速缓存称为 d-cache。既保留指令又包含数据的高速缓存称为对立的高速缓存( unified cache)。古代处理器包含独立的 i-cache和d-cache。这样做有很多起因。有两个独立的高速缓存,处理器可能同时读一个指令字和一个数据字。 i-cache通常是只读的,因而比较简单。通常会针对不同的拜访模式来优化这两个高速缓存,它们能够有不同的块大小,相联度和容量。应用不同的高速缓存也确保了数据拜访不会与指令拜访造成抵触不命中,反过来也是一样,代价就是可能会引起容量不命中减少。

编写高速缓存敌对的代码

  确保代码高速缓存敌对的根本办法。
  1)让最常见的状况运行得快。程序通常把大部分工夫都花在大量的外围函数上,而这些函数通常把大部分工夫都花在了大量循环上。所以要把注意力集中在外围函数里的循环上,而疏忽其余局部。
  2)尽量减小每个循环外部的缓存不命中数量。在其余条件(例如加载和存储的总次数)雷同的状况下,不命中率较低的循环运行得更快。

  思考如下的函数

int sumvec(int v[N])
{
    int i,sum = 0;
    
    for(i = 0;i<N;i++)
        sum +=v[i];
    return sum;
}

  首先,留神对于局部变量i和sum,循环体有良好的工夫局部性。当初考虑一下对向量v的步长为1的援用。一般而言,如果一个高速缓存的块大小为B字节,那么一个步长为k的援用模式(这里k是以字为单位的)均匀每次循环迭代会有$\min (1,(wordsize \times k)/B)$次缓存不命中。当k=1时,它取最小值,所以对v的步长为1的援用的确是高速缓存敌对的。

  例如,假如v是块对齐的,字为4个字节,高速缓存块为4个字,而高速缓存初始为空(冷高速缓存)。在这个例子中,对v[0]的援用会不命中,而相应的蕴含v[0] ~v[3]的块会被从内存加载到高速缓存中。因而,接下来三个援用都会命中。对v[4]的援用会导致不命中,而个新的块被加载到高速缓存中,接下来的三个援用都命中,依此类推。总的来说,四个援用中,三个会命中,在这种冷缓存的状况下,这是咱们所能做到的最好的状况了。

  总之,简略的 sumvec示例阐明了两个对于编写高速缓存敌对的代码的重要问题:第一,对局部变量的重复援用是好的,因为编译器可能将它们缓存在寄存器文件中(工夫局部性)。第二,步长为1的援用模式是好的,因为存储器层次结构中所有档次上的缓存都是将数据存储为间断的块(空间局部性)。

总结

  本章次要介绍了各种各样的存储系统及其原理,一般来说,较小、较快的设施在顶部,较大、较慢的设施在底部。因为编写良好的程序有好的局部性,大多数数据都能够从较高层失去服务,后果就是存储系统能以较高层的速度运行,但却有较低层的老本和容量。咱们能够通过编写有良好空间和工夫局部性的程序来显著地改良程序的运行工夫。例如,能够利用基于SRAM的高速缓存存储器。次要起因是从高速缓存取数据的程序比次要从内存取数据的程序运行得快得多。

  养成习惯,先赞后看!如果感觉写的不错,欢送关注,点赞,转发,谢谢!
如遇到排版错乱的问题,能够通过以下链接拜访我的CSDN。

**CSDN:[CSDN搜寻“嵌入式与Linux那些事”]

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理