你不知道的内存知识

世界上最快的捷径,就是脚踏实地,本文已收录【架构技术专栏】关注这个喜欢分享的地方。

一、CPU与内存

先铺垫几个概念,以免后面混乱:

  • Socket或Processor: 指一个物理CPU芯片,盒装还是散装的。上面有很多针脚,直接安装在主板上。

  • Core : 指在Processor里封装一个CPU核心,每个Core都是完全独立的计算单元,我们平时说的4核心CPU,指的就是Processor里面封装了4个Core。

  • HT超线程: 目前Intel与AMD的Processor大多支持在一个Core里并行执行两个线程,此时从操作系统看就相当于两个逻辑CPU(Logical Processor)。大多数情况下,我们程序里提到的CPU概念就是指的这个Logical Processor。

咱们先来看几个问题:

1、CPU可以直接操作内存吗?

可能一大部分老铁肯定会说:肯定的啊,不能操作内存怎么读取数据呢。

其实如果我们用这聪明的大脑想一想,咱们的台式主机大家肯定都玩过。上面CPU和内存条是两个完全独立的硬件啊,而且CPU也没有任何直接插槽用于挂载内存条的。

也就是说,CPU和内存条是物理隔离的,CPU并不能直接的访问内存条,而是需要借助主板上的其他硬件间接的来实现访问。

2、CPU的运算速度和内存条的访问速度差距有多大?

呵呵呵,这么说吧,就是一个鸿沟啊,CPU的运算速度与内存访问速度之间的差距是100倍。

而由于CPU与内存之间的速度差存在N个数量级的巨大鸿沟,于是CPU最亲密的小伙伴Cache 闪亮登场了。与DRAM 家族的内存(Memory)不同,Cache来自SRAM家族。

而DRAM与SRAM的最简单区别就是后者特别快,容量特别小,电路结构非常复杂,造价特别高。

而Cache与主内存之间的巨大性能差距主要还是工作原理与结构不同:

  • DRAM存储一位数据只需要一个电容加一个晶体管,SRAM则需要6个晶体管。

  • 由于DRAM的数据其实是被保存在电容里的,所以每次读写过程中的充放电环节也导致了DRAM读写数据有一个延时的问题,这个延时通常为十几到几十ns。

  • 内存可以被看作一个二维数组,每个存储单元都有其行地址和列地址。

    由于SRAM的容量很小,所以存储单元的地址(行与列)比较短,可以被一次性传输到SRAM中。DRAM则需要分别传送行与列的地址。

  • SRAM的频率基本与CPU的频率保持一致,而DRAM的频率直到DDR4以后才开始接近CPU的频率。

3、Cache 是怎么使用的?

其实Cache 是被集成到CPU内部的一个存储单元(平时也被我们称为高速缓存),由于其造价昂贵,并且存储容量远远不能满足CPU大量、高速存取的需求。

所以出于对成本的控制,在现实中往往采用金字塔形的多级Cache体系来实现最佳缓存效果。

于是出现了,一级Cache(L1 Cache)、二级Cache(L2 Cache)及三级Cache(L3 Cache)。每一级都牺牲了部分性能指标来换取更大的容量,目的也是存储更多的热点数据。

以Intel家族Intel SandyBridge架构的CPU为例:

  • L1 Cache容量为64KB,访问速度为1ns左右

  • L2Cache容量扩大4倍,达到256KB,访问速度则降低到3ns左右

  • L3 Cache的容量则扩大512倍,达到32MB,访问速度也下降到12ns左右(也比访问主存的105ns(40ns+65ns)快一个数量级)

L3 Cache是被一个Socket上的所有CPU Core共享的,其实最早的L3 Cache被应用在AMD发布的K6-III处理器上,当时的L3 Cache受限于制造工艺,并没有被集成到CPU内部,而是被集成在主板上,如图:

从上图我们也能看出来,CPU如果要访问内存中的数据,则需要经过L1、L2、L3三道关卡,就是这三个Cache中都没有需要的数据,才会从主内存中直接进行读取。

最后我们来看下Intel Sandy Bridge CPU的架构图:

二、多核CPU与内存共享的问题

问题: Cache一致性问题

多核CPU共享内存的问题也被称为Cache一致性问题。

其实就是多个CPU核心看到的Cache数据应该是一致的,在某个数据被某个CPU写入自己的Cache(L1 Cache)以后,其他CPU都应该能看到相同的Cache数据。

如果在自己的Cache中有旧数据,则抛弃旧数据。

考虑到每个CPU都有自己内部独占的Cache,所以这个问题与分布式Cache保持同步的问题是同一类问题

目前业界公认的解决一致性问题的最佳方案就是Intel 的MESI协议了,大多数SMP架构都采用了这一方案。

解决方案:MESI

不知道大家还记得Cache Line 吗,就是我们常说的高速缓存中缓存条目里面的那个缓存行。

其实仔细想想,在进行I/O操作从来不以字节为单位,而是以块为单位,有两个原因:

  • I/O 操作比较慢,所以读一个字节与读连续N个字节的花费时间基本相同
  • 数据访问一般都具有空间连续的特征

所以CPU针对Memory的读写也采用了类似于I/O块的方式

实际上,CPU Cache(高速缓存)里最小的存储单元就是Cache line(缓存行),Intel CPU 的一个Cache Line存储64个字节。

每一级Cache都被划分为很多组Cache Line,典型的情况就是4条Cache Line为一组。

当Cache从Memory中加载数据时,一次加载一条Cache Line的数据

如图我们可以看到,每个Cache Line 头部都有两个Bit来标识自身状态,总共四种:

  • M(Modified):修改状态,在其他CPU上没有数据的副本,并且在本CPU上被修改过,与存储器中的数据不一致,最终必然会引发系统总线的写指令,将Cache Line中的数据写回Memory中。

  • E(Exclusive):独占状态,表示当前Cache Line中的数据与Memory中的数据一致,此外,在其他CPU上没有数据的副本。

  • S(Shared):共享状态,表示Cache Line中的数据与Memory中的数据一致,而且当前CPU至少在其他某个CPU中有副本。

  • I(Invalid):无效状态,在当前Cache Line中没有有效数据或者该Cache Line数据已经失效,不能再用;当Cache要加载新数据时,优先选择此状态的Cache Line,此外,Cache Line的初始状态也是I状态

在对Cache(高速缓存)的读写操作引发了Cache Line(缓存行)的状态变化,因而可以将其理解为一种状态机模型。

但MESI的复杂和独特之处在于状态有两种视角:

  • 一种是当前读写操作(Local Read/Write)所在CPU看到的自身的Cache Line状态及其他CPU上对应的Cache Line状态
  • 另一种是一个CPU上的Cache Line状态的变迁会导致其他CPU上对应的Cache Line状态变迁。

如下所示为MESI协议的状态图:

具体MESI的实现过程可以看我另一篇文章: 看懂这篇,才能说了解并发底层技术

深入理解不一致性内存

MESI协议解决了多核CPU下的Cache一致性问题,因而成为SMP架构的唯一选择,而SMP架构近几年迅速在PC领域(X86)发展。

SMP架构是一种平行的架构,所有CPU Core都被连接到一个内存总线上,它们平等访问内存,同时整个内存是统一结构、统一寻址的。

如下所示给出了SMP架构的示意图:

随着CPU核心数量的不断增加,SMP架构也暴露出天生的短板,其根本瓶颈是共享内存总线的带宽无法满足CPU数量的增加,同时,在一条“马路”上通行的“车”多了,难免会陷入“拥堵模式”。

不知道你是否听说过总线风暴,可以看下:总线风暴

在这种情况下,分布式解决方案应运而生,系统的内存与CPU进行分割并捆绑在一起,形成多个独立的子系统,这些子系统之间高速互联,这就是NUMA(None Uniform Memory Architecture)架构,如下图所示。

可以看出,NUMA架构中的内存被分割为独立的几块,被不同CPU私有化了。

因此在CPU访问自家内存的时候会非常快,在访问其他CPU控制的内存数据时,则需要通过内部互联通道访问。

NUMA架构的优点就是其伸缩性,就算扩展到几百个CPU也不会导致性严重的下降。

NUMA技术的特点

在NUMA架构中引入了一个重要的新名词——Node

一个Node由一个或者多个Socket Socket组成,即物理上的一个或多个CPU芯片组成一个逻辑上的Node

我们来看一个Dell PowerEdge系列服务器的NUMA的架构图:

从上图可以看出其特点:

  • 4个处理器形成4个独立的NUMA Node由于每个Node都为8 Core,支持双线程

  • 每个Node里的Logic CPU数量都为16个,占每个Node分配系统总内存的1/4

  • 每个Node之间都通过Intel QPI(QuickPath Interconnect)技术形成了点到点的全互联处理器系统

NUMA这种基于点到点的全互联处理器系统与传统的基于共享总线的处理器系统的SMP还是有巨大差异的。

在这种情况下无法通过嗅探总线的方式来实现Cache一致性,因此为了实现NUMA架构下的Cache一致性,Intel引入了MESI协议的一个扩展协议——MESIF

针对NUMA的支持

NUMA架构打破了传统的“全局内存”概念,目前还没有任意一种编程语言从内存模型上支持它,当前也很难开发适应NUMA的软件。

Java在支持NUMA的系统里,可以开启基于NUMA的内存分配方案,使得当前线程所需的内存从对应的Node上分配,从而大大加快对象的创建过程

在大数据领域,NUMA系统正发挥着越来越强大的作用,SAP的高端大数据系统HANA被SGI在其UV NUMA Systems上实现了良好的水平扩展

在云计算与虚拟化方面,OpenStack与VMware已经支持基于NUMA技术的虚机分配能力,使得不同的虚机运行在不同的Core上,同时虚机的内存不会跨越多个NUMA Node

posted @ 2020-10-21 09:26  鲁大猿  阅读(982)  评论(5编辑  收藏  举报