背景知识

Memory Hierarchy

众所周知,对于不同的存储设备,更高的性能意味着更高的成本和更小的容量。随着 CPU 越做越快,CPU 和主存之间的速度差距正在不断扩大。好在,软件的局部性原理 拯救了这一切,在现代计算机体系中通过 Memory Hierarchy 的设计,使得系统在性能、成本和制造工艺之间作出取舍,从而达到了一个平衡。

下图是现在可以看到的常见的存储器层次机构:

Memory Hierarchy
Memory Hierarchy

下图是对应不同层次的存储器的不同访问延时的一个总结表格:

Cache Latency
Cache Latency

可以看到,寄存器和 TLB 可以达到和 CPU 相同频率工作,但是从 L1 Cache开始,每过一级访问延时就会提高一个甚至两个数量级。因此,在现代计算机体系结构中,缓存无处不在。L1 Cache 是主存的缓存,主存是外存的缓存。良好的运用缓存,可以大幅提高计算机的工作性能。

上面关于访问延时的数字是一个粗略的数量级估计,关于更加详细的研究和测试,可以看我的另一篇博文 Key Numbers Every Programmer Should Know,此处不再详述。

多核结构

随着多核架构的普及,对称多处理器 (SMP) 系统成为主流。一个物理 CPU 可以存在多个物理 core。以下图的 Intel Core i7的为例,一个 x86 CPU 有多个core,每个 core 有自己的寄存器,自己的L1数据缓存和L1指令缓存,自己的 L2 统一缓存。所有的 core 之间共享一个统一的 L3 缓存,主存作为 L3 Cache 的缓存。

Intel Core i7 Cache Hierarchy
Intel Core i7 Cache Hierarchy

除了物理的 core 之外,每个 core 也可以存在多个硬件线程,也就是HT (Hyper Thread)。以下图为例,1 个 x86 CPU 有 4 个物理 Core,每个 Core 有两个 HT。从硬件的角度,上图的 L1 和 L2 Cache 都被两个 HT 共享,且在同一个物理 Core。而 L3 Cache 则在物理 CPU 里,被多个 Core 来共享。 而从 OS 内核角度,每个 HT 都是一个逻辑 CPU,因此,这个处理器在 OS 来看,就是一个 8 个 CPU 的 SMP 系统。

Cache Hyper Thread Hierarchy
Cache Hyper Thread Hierarchy

关于 Hyper Thread 的详细背景,可以参考我的另一篇博文 Hyper Thread,此处不再详述。

NUMA 架构

一个 SMP 系统,按照其 CPU 和内存的互连方式,可以分为 UMA (均匀内存访问) 和 NUMA (非均匀内存访问) 两种架构。 其中,在多个物理 CPU 之间保证 Cache 一致性的 NUMA 架构,又被称做 ccNUMA (Cache Coherent NUMA) 架构。

以 x86 为例,早期的 x86 就是典型的 UMA 架构。例如下图,四路处理器通过 FSB (前端系统总线) 和主板上的内存控制器芯片 (MCH) 相连,DRAM 是以 UMA 方式组织的,延迟并无访问差异

x86 UMA
x86 UMA

然而,这种架构带来了严重的内存总线的性能瓶颈,影响了 x86 在多路服务器上的可扩展性和性能。

因此,从 Nehalem 架构开始,x86 开始转向 NUMA 架构,内存控制器芯片被集成到处理器内部,多个处理器通过 QPI 链路相连,从此 DRAM 有了远近之分。 而 Sandybridge 架构则更近一步,将片外的 IOH 芯片也集成到了处理器内部,至此,内存控制器和 PCIe Root Complex 全部在处理器内部了。 下图就是一个典型的 x86 的 NUMA 架构:

NUMA典型架构
NUMA典型架构

由于 NUMA 架构的引入,以下主要部件产生了因物理链路的远近带来的延迟差异:

  • Cache

    除物理 CPU 有本地的 Cache 的层级结构以外,还存在跨越系统总线 (QPI) 的远程 Cache 命中访问的情况。需要注意的是,远程的 Cache 命中,对发起 Cache 访问的 CPU 来说,还是被记入了 LLC Cache Miss。

  • DRAM

    在两路及以上的服务器,远程 DRAM 的访问延迟,远远高于本地 DRAM 的访问延迟,有些系统可以达到 2 倍的差异。 需要注意的是,即使服务器 BIOS 里关闭了 NUMA 特性,也只是对 OS 内核屏蔽了这个特性,这种延迟差异还是存在的。

  • Device

    对 CPU 访问设备内存,及设备发起 DMA 内存的读写活动而言,存在本地 Device 和远程 Device 的差别,有显著的延迟访问差异。

因此,对以上 NUMA 系统,一个 NUMA 节点通常可以被认为是一个物理 CPU 加上它本地的 DRAM 和 Device 组成。那么,四路服务器就拥有四个 NUMA 节点。 如果 BIOS 打开了 NUMA 支持,Linux 内核则会根据 ACPI 提供的表格,针对 NUMA 节点做一系列的 NUMA 亲和性的优化。

关于 NUMA 架构的详细内容,可以参考我的另一篇博文 NUMA 架构,此处不再赘述。

Cache Line

Cache Line 是 CPU 和主存之间数据传输的最小单位。当一行 Cache Line 被从内存拷贝到 Cache 里,Cache 里会为这个 Cache Line 创建一个条目。 这个 Cache 条目里既包含了拷贝的内存数据,即 Cache Line,又包含了这行数据在内存里的位置等元数据信息。

关于 Cache 结构的详细内容,可以参考我的另一篇博客 Cache Memory,此处不再赘述。

Cache Coherency

如前所述,在 SMP 系统里,每个 CPU 都有自己本地的 Cache。因此,同一个变量,或者同一行 Cache Line,有在多个处理器的本地 Cache 里存在多份拷贝的可能性,因此就存在数据一致性问题。 通常,处理器都实现了 Cache 一致性 (Cache Coherence)协议。如历史上 x86 曾实现了 MESI 协议, 以及 MESIF 协议。

假设两个处理器 A 和 B, 都在各自本地 Cache Line 里有同一个变量的拷贝时,此时该 Cache Line 处于 Shared 状态。当处理器 A 在本地修改了变量,除去把本地变量所属的 Cache Line 置为 Modified 状态以外, 还必须在另一个处理器 B 读同一个变量前,对该变量所在的 B 处理器本地 Cache Line 发起 Invaidate 操作,标记 B 处理器的那条 Cache Line 为 Invalidate 状态。 随后,若处理器 B 在对变量做读写操作时,如果遇到这个标记为 Invalidate 的状态的 Cache Line,即会引发 Cache Miss, 从而将内存中最新的数据拷贝到 Cache Line 里,然后处理器 B 再对此 Cache Line 对变量做读写操作。

本文中的 Cache Line 伪共享场景,就基于上述场景来讲解,关于 Cache 一致性协议更多的细节,请参考我的另一篇博文 Cache Coherency

False Sharing

ECharts

参考资料