缓存技术:从CPU Cache到AI KV Cache (一)

缓存不仅是单独的组件,还是计算机系统长期对抗速度差的产物。

一、引言:速度差和缓存的基本逻辑

计算机系统中一直都存在部件速度不匹配的问题。

CPU比内存快几个数量级,内存比磁盘快几个数量级。

网络络延迟的范围很大:数据中心内部访问可能只需要几微秒,而跨地域访问往往达到数十到数百毫秒,甚至超过本地磁盘访问延迟。

而从部件的设计目标和底层物理原理来看,这种速度差几乎是天然存在的:

CPU是为了计算而生的,其上集成大量晶体管,通过逻辑门实现与非、或非、加法器等基本计算单元,晶体管开关和门级延迟是几皮秒、几十皮秒量级。

磁盘是为了低成本、大容量的持久性存储而生的。

早期机械硬盘需要磁头在旋转盘片上读写磁畴,磁头需要进行寻道和旋转等待,因此访问延迟通常在毫秒级。

现在流行的SSD虽然使用电子存储,延时较机械硬盘有量级的提升,但因为它的NAND的擦写、电荷泵、页块管理以及FTL映射等机制,所以它的访问延迟通常在几十到上百微秒量级。

从存储层次的角度看,内存可以看作是磁盘数据的缓存角色。

它让数据离CPU更近、被访问得更快。内存通过电容充放电临时保存数据,能够在成本、容量和速度之间取得折中,提供GB级容量和纳秒级访问延迟。

这些部件的技术基因决定了它们的速度天然不在一个量级上。如果完全依赖慢速部件自身变快,系统性能就会长期受制于最慢的部件。

不过,工程师们发现了一个重要规律:程序执行时的数据访问不是随机的,而是具有局部性的特征。

- 时间局部性:刚用过的数据,后面很可能再被用到。

- 空间局部性:刚用过的数据旁边的数据,也可能被用到。

所以工程师们不再追求让所有数据都同样快,而是让经常用的数据先快起来。

这就是缓存的核心思想:缓存不是简单的"拿空间换时间" 而是 利用局部性原理,用一小块快速介质存放热点数据,让大多数访问绕过慢速部件。

如果数据的访问完全随机,那么缓存没有任何意义。正因为访问行为存在可利用的统计规律,缓存才真正有效。

从CPU到数据库,从CDN到大模型,缓存技术的历史,就是不断发现新的局部性、构造新的缓存层,并承受新的系统代价的过程。

缓存的本质,并不是存的更快,而是尽量少搬数据。

二、硬件缓存时代:在 CPU和内存之间加一层

1965年,剑桥大学的计算机科学家Maurice Wilkes在论文《Slave Memories and Dynamic Storage Allocation》中,首次提出了现代CPU Cache的雏形概念。

当时的主存是磁芯存储器(磁芯),速度慢但便宜,Wilkes的方案是用少量高速磁芯或晶体管存储器做从属,Wilkes给它取了个名字,叫Slave Memory(从属存储器):

从属存储器会自动缓存从低速主存读取的数据,并在局部性有效期间尽可能保留,后续再次访问时无需重复访问慢速主存。

由于从属存储器容量远小于主存,无法永久保存所有数据,必须在硬件中内置替换覆写算法,通过替换策略动态淘汰低热点数据。

在访问局部性良好的场景下,大部分被访问过的数据能长期留在高速从属存储器中,显著提升系统整体运行速度。实际提速幅度取决于程序的内存访问统计特征。

在文章中,描述Slave Memory的缓存方案用到的"标记位 Tag、缓存命中 / 缺失、写回策略、程序局部性、动态替换",全部沿用至今,是Cache技术的理论源头。

那么现代计算机的Cache方案是怎么样的呢?

现代计算机一般都是多核CPU,在 CPU 内部,每个 Core(核心)都包含独立的执行单元,用于完成指令计算、逻辑运算与数据处理。CPU引入多级缓存机制来减少访存时延。

****- L1 Cache(一级缓存)****位于距离Core 最近的位置,每个Core独享。通常分为指令缓存和数据缓存。速度最快、容量最小。

****- L2 Cache(二级缓存)****通常也由单个Core 独享,容量比L1更大,但访问速度略慢。可用于缓存更多热点数据。

****-L3 Cache(三级缓存)****是Last Level Cache(LLC),通常由所有 Core 共享。容量最大,用于减少多个Core访存时的带宽压力,提高多核协同效率。

我们来直观看下CPU上的缓存的物理形态吧!

下图是AMD Zen3 CPU Core 的 Die Shot芯片裸片显微图。

左边橙色部分是CPU Core的计算单元。

离计算单元最近的是各32KB的L1 数据缓存和L1 指令缓存。

中间有一片区域是512KB的L2 缓存,右边有非常大一片区域用于4MB的L3缓存。

可以看到CPU芯片面积的一大半都被用于缓存了,这就是CPU为了速度愿意付出的面积代价。

读者们可以通过下表感受一下计算机内部的访存时延量级。

不过,缓存虽好,也带来了两个经典问题:

1. 缓存一致性问题 Cache Coherence

在多核 CPU 中,当多个Core同时缓存同一份数据时,就可能出现:

一个Core修改了数据,而另一个Core仍然在使用旧缓存数据。

这就会导致:数据不一致、并发行为不可预测等麻烦。

例如:

Core0:

x = 1

Core1:

while(x == 0)

如果 Core1 的缓存里一直是旧值 0,程序可能永远循环。

因此现代 CPU 必须实现MESI、MOESI、MESIF等Cache Coherence Protocol(一致性协议)。这些协议能保证多个Core看到的数据一致。

MESI协议通过缓存行状态(Modified/Exclusive/Shared/Invalid)和监听机制,让每个Core在修改数据前,先通知其他Core失效其副本。

这也是为什么多线程同步(锁、原子操作、memory barrier)本质上很多时候是在和 Cache coherence 系统协同工作的原因。

如果没有缓存一致性协议,程序员将被迫对所有共享变量使用'禁用缓存'的访问方式,性能将退化到原始内存访问级别。

2. 如何写 缓存友好程序 (Cache Friendly Programming)

程序员在编程时,需要考虑到局部性、以及缓存的特征:

CPU并不是按变量缓存数据,而是按 Cache Line(通常64B)为单位进行缓存和传输,

才能开发出性能好的软件。

缓存优化能力是写出高性能代码的核心能力。

(1)空间局部性

连续访问的数据更容易被缓存。

比如对数组的访问最好采用顺序访问的形式。

因为数组是连续存储的,而CPU通常会按整个Cache Line进行预取和缓存,所以后续元素已经在缓存里了,所以对它的访问会更快。

而随机访问会导致Cache Miss增加,所以CPU会不断等待从DRAM获取数据进缓存,因此CPU会频繁停顿等待内存数据返回。

(2)时间局部性

刚访问过的数据,很可能马上再次访问。

例如:

sum += x;

sum += x;

sum += x;

变量 x 会一直留在 Cache 中。

因此:重复使用热点数据、减少大范围扫描都会提升性能。

(3)Cache Line 与伪共享

多个线程即使修改不同变量,只要这些变量落在同一个 Cache Line 中,CPU 仍然会不断同步缓存。这叫:False Sharing(伪共享)。

伪共享会严重降低并发性能。

这种情况可采用padding(缓存行填充)对齐数据结构,避免线程共享邻近变量的方法来处理。

再说的具体一点:

比如两个线程分别频繁修改x和y,而x和y在同一个64字节的Cache Line里。每次修改x,另一Core的缓存行就失效,导致性能下降几倍到几十倍。

Padding让它们分开到不同Cache Line,问题消失。

所以,现代高性能编程已经不只是看算法复杂度,而更需要同时考虑内存访问模式和Cache 行为这些实际工程特点。

相关推荐
朝阳58114 小时前
树莓派跑了个 M3U8 下载服务,内存从 600MB 降到 2MB
性能优化·rust
梵得儿SHI14 小时前
SpringCloud 进阶拓展:性能优化指南(缓存三大问题 + 分库分表入门)
spring cloud·缓存·微服务·性能优化·高并发·分库分表·数据库优化
木雷坞14 小时前
AI Gateway 接入大模型服务后首 token 慢排查:镜像、模型缓存和 GPU 节点
人工智能·缓存·gateway
cfm_291414 小时前
了解Redis
数据库·redis·缓存
山峰哥14 小时前
索引策略与SQL优化:从Explain对比到生产调优的完整方法论
android·java·数据库·sql·性能优化·深度优先
全球通史15 小时前
Jetson Nano 双摄像头芯片检测视觉系统:小尺度难定位问题解决,从零开始实现教程说明
嵌入式硬件·算法·ubuntu·性能优化
闪电悠米15 小时前
黑马点评-优惠券秒杀-01_redis_global_id
数据库·redis·缓存
爱喝水的鱼丶16 小时前
SAP-ABAP:条件判断与循环控制语句(7篇)第七篇:性能优化:条件与循环代码的常见性能瓶颈与优化方案
学习·算法·性能优化·sap·abap
2301_780789661 天前
高防cdn如何缓存网页静态资源
java·spring·web安全·缓存·kubernetes·ddos