cpuMemory(1)

CpuMemory(1)


概述

  1. 为了了解更多内存和cpu相关的内容,阅读 What Every Programmer Should Know About Memory 文档,尽可能地理解

Cpu Cache

  1. 早期的Cpu访问速度仅仅和内存总线一个级别,内存访问只比寄存器访问慢一点点。但是到了90年代,CPU技术有了突破性的进展( INTEL 1982年推出80286芯片 ),cpu的吞吐量大大提升,但是内存总线和RAM存储的速度并没有按比例提升,并不是因为快速的RAM存储无法制造,而是基于成本的原因( 例如结构复杂的SRAM,相对于DRAM,单位体积造价高而且容量小 ),无法采用。

  2. 如果在这样两种方案之间选择: 拥有一小块快速RAM存储的机器结构拥有大量相对快速的RAM存储的机器结构,后者毫无疑问会胜利,既可以提供相当大的存储空间,而且也降低了访问第二存储介质(通常是硬盘)的成本

  3. 限制在于第二存储介质的访问速度,也就是硬盘,用来存储RAM工作集换出的部分,访问硬盘上的存储数据甚至比访问DRAM的数据要慢上好几个数量级

  4. 不过,SRAM和DRAM是可以共存的,一台电脑在大量的DRAM存储之外,还可以拥有一小块快速的SRAM存储,可能的实现方案是在处理中划分出一块指定的地址空间容纳SRAM和DRAM,这样系统任务就会优化数据分配以更好的利用内存,基本上,SRAM是作为处理器的额外寄存器使用。

  5. 但是这种实现方案是不可行的。它要求每个处理器软件中管理自身区域的内存分配。但是每个处理器所拥有的这一块内存区域的大小是不同的,而程序的每一个部分都要求共享SRAM内存,由于这种同步的要求反而增加了开销。简单来说,增加SRAM快速存储带来的收益会完全被管理这些资源的开销给抵消掉。

  6. 因此,代替方案就是将SRAM交由处理器控制和使用,而不是作为处理器的一部分存在。这种情况下,SRAM存储通常是存储主存中即将被处理器使用的数据的临时拷贝。这一点之所以可能实现,是因为程序代码和数据都具有时间和空间局部性,这意味着短时间内的代码和数据重用。

    1. 空间局部性:在特定时间一块特定的内存区域被使用之后,那么这块内存区域附近的内存也可能在不久之后被使用到,因此会参考当前内存引用区域的大小和形状,加速随后的内存访问。
    2. 时间局部性:如果某一时刻引用了特定的存储位置,那么有可能这块内存区域在不久之后会再次被使用到。相邻引用之间在引用时间上接近同一存储位置。这种情况下,普遍会在更快的存储介质中(例如SRAM)存储一份主存中的数据拷贝,降低随后访问同一内存的时间成本。
    3. 对于代码来说,处于循环中的代码会一次又一次地执行,这就是代码的空间局部性;这种情况下访问的数据也是局限在一小块地址范围,也就是数据的空间局部性
    4. 和上面类似,循环代码中调用的函数可能位于不同的内存区域,但是它们在短时间内被连续地调用 ,这就是代码时间局部性。而数据的时间局部性表现为:在一定时间内使用的内存总量可能是一致的,但是由于RAM的随机访问特性,使用的内存区域可能并不是物理连续的。
  7. 可以用一个简单的计算来展示缓存在性能上的提升效果:

    1. 假设访问一次主存的数据需要200个时钟周期,而访问缓存的数据只需要15个时钟周期;
    2. 那么访问100个数据100次的代码,在主存的访问时间消耗为2000000个时钟周期,访问缓存只需要168500个时钟周期,效率的提升达到了 91.5%
    3. 使用得当的缓存会带来极大的收益
  8. 用作缓存的SRAM存储的容量通常比主存小很多倍,根据经验,缓存容量和主存容量的比值大概是 1:1000 ;在这种缓存容量有限的情况下,就需要很好的策略来控制在任意时间点上什么内容值得被缓存。由于工作集中的所有数据并不是都在同一时刻被使用,也就是微观上并行的,因此我们可以通过技术手段临时地替换一部分缓存中的数据;而且**这种操作和CPU执行任务是异步的,**减少了访问主存的开。


CPU Caches in the Big Picture

  1. 在了解CPU缓存的细节之前,可以先看看CPU缓存在系统中所处的结构位置:
  2. 上图展示了最小化的缓存结构,代表了早期计算机的缓存架构模式。Cpu内核不再直接与主存连接,所有的数据加载和存储的动作都通过缓存实现。而Cpu和缓存之间存在一条特殊的,快速访问的连接。在这个简化的模型中,缓存和主存通过系统总线相连,而系统总线也是主存计算机其他组件连接交流的地方。
  3. 尽管过去几十年大部分的计算机的架构模式都是冯·诺伊曼结构,在这种架构模式下,由于共享系统总线,指令获取和数据操作不能同时进行,成为了这个架构模式的瓶颈,经常会限制计算机的性能表现。经验表明,将指令和数据使用的缓存区分开是有好处的,自 1993 起 Intel 就采用这种模式的缓存设计并坚持做下去。指令和数据各自使用的内存区域彼此之间非常独立,因为独立的缓存区域工作效率更高。近几年的计算机发展过程中,缓存的另一个优势也慢慢体现出来了:大部分处理器的指令解码过程比较慢,缓存解码指令可以加速这个过程,尤其是在指令流水线为空或者**分支预测**失败以及无法预测执行分支的情况下。
  4. 引入缓存之后系统结构变得更加复杂,主存和缓存之间的速度差异再次提升,因此诞生了二级缓存 – 比一级缓存慢但是容量大,因为一味地加大一级缓存在成本上是不可取的。如今,甚至有些计算机引入了三级缓存,在单个cpu上继续增加多级缓存不是没有可能。
  5. 具备三级缓存的计算机结构原理图如上,实际的缓存和CPU设计不一定是这样的。可以看到cpu具有L1,L2,L3三级缓存,L3缓存直接与总线连接间接地和主存交流数据。L1d Cache 是一级数据缓存 , L1i Cache 是一级指令缓存,像前面提到的那样,它们是独立的。
  6. 此外,现在的CPU已经发展出了多核以及单核多线程的形态。处理的单核和单线程之间的区别是:
    1. 每一个处理器内核都具有一部分硬盘资源的副本,在不共享资源的情况下,每个内核都是完全独立运行的,没有交叉;
    2. 线程共享所有处理的资源,intel 处理器的线程实现只有单独的线程寄存器,有些寄存器是共享的
  7. 现代多处理器,多核多线程的cpu模型大致如上图所示:
    1. 具有两个处理器,每个处理器有两个内核,每个内核划分出两条线程。
    2. 每个内核具有各自的L1缓存,因此两个处理器之间的缓存也是独立的,而核心内的线程则是共享单核的所有资源包括缓存
    3. 要注意,两个处理器之间是不共享缓存的

高层次的缓存操作

  1. 为了更好地体现使用缓存的价值,需要结合计算机架构和RAM存储技术中的缓存内容一起来讲解

  2. 默认情况下所有CPU读写的的数据都存储在缓存中,即便存在一些无法缓存的内存区域,也仅仅是系统层面需要关心的事情,对于程序员来说是不可见的。

  3. 当CPU需要查找一个数据时,首先会在缓存中查找,但是之前也提到过,基于容量大小和成本的原因,缓存是不可能存下所有主存中的数据的,而是存储了所有主存中数据的内存地址,因此每一个主存中的数据都通过缓存中的地址信息被标记起来,这样一来,所有主存数据的读写操作都被映射成在缓存中进行数据内存地址的查找。缓存的内存地址可以是虚拟地址也可以是物理地址,这取决于缓存的实现细节。

  4. 也正是由于需要存储标记信息,对于每一块特定的内存区域来说,需要额外的内存空间,选择字长作为缓存的粒度就显得十分低效。而且缓存也是基于空间局部性的特点实现的,相邻的内存区域由于可能被一起使用因此也会被加载到缓存里面。在RAM存储模块中,如果它们能够在不要CAS或RAS信号的情况下,在一行中传输多个字信息会显得十分高效。所以,实际上存储在缓存中数据不是单独的字符而是由一系列连续的字符组成的行数据(或者叫做行?)。在在早期计算中缓存中,这些的长度为32字节(8bit 为一个字节),现在大都是64字节长。意味着,在64字节宽度的内存总线中,每条缓存行中可以传输8个字长的数据,这是比较高效的做法

    DRAM数据访问过程:

    1. 一次数据读取周期从主存控制器在地址总线中将行地址标记为可用状态然后降低RAS(Row Address Strobe)信号开始,选择一个地址行打开,只要RAS信号维持在低的状态,地址行就不会关闭;设置行地址会让磁片开始锁住地址行
    2. 在CAS(Column Address Strobe)之间存在一个 t RCD(RAS-to-CAS Delay) 时间差,在这个时间差之后,开始发送CAS信号,锁定列地址
    3. 至此,进行读写的地址信息已经确定了,但是就像RAS信号和CAS信号之间的延迟时间一样,磁盘准备所需的数据也要一些时间,称为 CL(CAS Latency),从CAS与读取命令发出到第一批数据输出的时间段,在这个时间段之后,才开始真正地传输数据
    4. 整个过程如下图所示:
    5. 可以看出,示例的一次数据读取过程,RAS信号发出到CAS信号发出之间是 2 CLK,CAS信号到第一次数据传输是 2CLK,传输8个字节的数据花费了4CLK;也就意味着如果要高效进行数据传输,传输数据花费的时间最好大于准备数据传输的时间,简单来说,4个字节的数据,如果在上述准备时间 4 CLK之后,每次只花费 1CLK传输1字节的数据,传输四次才完成4个字节的传输,其中有 4CLK *3 = 12 CLK的时间是被浪费了的
    6. 所以结论就是:基于这种数据传输的过程,一次性写入一整行( 取决于数据总线的宽度 )数据会更加高效
  5. 当处理器需要内存中的内容,会将一整条缓存行加载到 L1d 缓存中,cpu通过根据缓存行容量进行掩码运算得出缓存行的内存地址(//TODO 不理解)。对于一个64字节大小的缓存行来说,低六位都是0,作为缓存行中的索引使用。剩余的缓存容量用于定位缓存行在缓存中的地址和作为标签(tag)使用,实际应用中一个地址划分成三部分:Tag,CacheSet,Offset,如下图:

  6. 容量为 2^O 大小的缓存行,低 O 位用作缓存行地址偏移量。//TODO