ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

DRAM 的工作原理:一座需要不停维护的巨型仓库

DRAM 的工作原理:一座需要不停维护的巨型仓库

一、生活里的例子:图书馆的密集书库

一座大型图书馆,书不放在阅览室,而是放在后面的密集书库:

  1. 你要一本书,管理员先走到对应的书库房间
  2. 把一整排书架推出来(密集书架平时是合拢的)
  3. 从这一排里取出你要的那本
  4. 如果下一本书在同一排,直接再拿就行,很快
  5. 如果在另一排,要先把这排推回去,再推出新的一排,慢很多
  6. 另外,书会慢慢受潮,管理员要定期翻晒
图书馆DRAM
一间书库Bank
推出一整排书架激活(ACT):把一整行读到行缓冲区
推出来的那一排行缓冲区(Row Buffer)
从这排里拿某本书**读(READ)**某一列
推回去预充电(PRE):关闭这一行
定期翻晒刷新(REF)
好几间书库可以同时工作多个 Bank 并行

记住两点就理解了 DRAM 的大部分性能特征:同一行里连续取很快,换行很慢。


二、层级结构:从内存条到电容

CPU 的内存控制器 └─ 通道(Channel) 双通道 = 两条独立的数据通路 └─ 内存条(DIMM) └─ Rank 一组芯片共同组成 64 位宽的数据 └─ 芯片(Chip) 内存条上那一颗颗黑色方块 └─ Bank Group → Bank └─ 存储阵列:行(Row)× 列(Column) └─ 单元:1 个晶体管 + 1 个电容
层级典型规模(DDR4 为例)作用
通道台式机常见 2 个带宽叠加
Rank每条 1~2 个8 颗 ×8 芯片拼成 64 位
Bank每颗芯片 16 个(4 个 Bank Group)可以并行工作
行每个 Bank 几万行激活的基本单位
行大小每颗芯片约 1KB,整个 Rank 约 8KB一次激活打开这么多数据

存储阵列长什么样

列 0 列 1 列 2 ... 列 N 行 0 ───── ● ● ● ● ← 字线(Word Line) 行 1 ───── ● ● ● ● 行 2 ───── ● ● ● ● ... │ │ │ │ 位线 位线 位线 位线(Bit Line) │ │ │ │ ┌─────────────────────────┐ │ 灵敏放大器 = 行缓冲区 │ ← 一次装下整整一行 └─────────────────────────┘ │ 列选择 ▼ 数据 I/O
  • 字线横着走:选中某一行,这一行所有晶体管一起打开
  • 位线竖着走:把每个电容的电荷引到底部的灵敏放大器
  • 灵敏放大器就是行缓冲区:放大信号,同时暂存这一整行

三、一次读取的完整过程

三个核心命令

命令做什么
ACT(激活)选中一行 → 电荷流到位线 → 灵敏放大器放大并锁存整行
READ / WRITE从行缓冲区里选出某几列,传出或写入
PRE(预充电)把行数据写回电容,关闭这一行,位线恢复到中间电压,准备下一次激活

上一篇讲过"读取是破坏性的":激活时电容里的电荷已经被分走,正是灵敏放大器把放大后的结果写回单元,数据才得以保留。

三种情况,三种速度

情况需要的步骤速度
行命中:要的行已经在行缓冲区里READ最快
行空闲:Bank 里没有打开的行ACT → READ中等
行冲突:Bank 里打开的是别的行PRE → ACT → READ最慢
行命中: [READ]────→ 数据 行空闲: [ACT]──────[READ]────→ 数据 行冲突: [PRE]──────[ACT]──────[READ]────→ 数据

四、时序参数:内存条上那串数字

内存条标签上常见DDR4-3200 CL16-18-18-38这样的字样:

参数全称含义
CLCAS Latency发出 READ 后,等多久数据出来
tRCDRAS to CAS Delay发出 ACT 后,等多久才能 READ
tRPRow Precharge发出 PRE 后,等多久才能 ACT 新的一行
tRASRow Active Time一行至少要打开多久才能关

这些数字的单位是时钟周期,要换算成纳秒才有意义:

DDR4-3200:数据速率 3200 MT/s,时钟 1600 MHz 一个周期 = 1 / 1600MHz = 0.625 ns CL16 → 16 × 0.625 = 10 ns tRCD18 → 11.25 ns tRP18 → 11.25 ns
情况估算(只算 DRAM 本身)
行命中CL ≈ 10 ns
行空闲tRCD + CL ≈ 21 ns
行冲突tRP + tRCD + CL ≈ 32 ns

再加上 CPU 查完各级缓存、经过内存控制器排队的时间,一次真正的内存访问通常在 60~100 ns。

一个常被误解的点

内存CL换算成纳秒
DDR4-2400 CL151512.5 ns
DDR4-3200 CL161610 ns
DDR5-6000 CL303010 ns

DDR5 的 CL 数字看起来是 DDR4 的两倍,但因为时钟更快,实际延迟差不多。几十年来,内存的带宽提升了几十倍,延迟却几乎一直停在十几纳秒的量级 —— 电容充放电、长导线传信号,这些物理过程很难再快多少。


五、带宽:DDR、预取和突发

DDR 是什么意思

Double Data Rate(双倍数据速率):时钟的上升沿和下降沿各传一次数据。所以 DDR4-3200 的时钟是 1600MHz,每秒传输 3200M 次。

带宽计算

DDR4-3200,单通道 64 位: 3200M 次/秒 × 8 字节 = 25.6 GB/s 双通道:51.2 GB/s

存储阵列那么慢,怎么喂饱这么快的接口?

靠预取(Prefetch):存储阵列内部一次并行读出很多位,再由接口快速地一个接一个送出去。

代际预取一次突发(Burst)
DDR48nBL8:连续 8 次传输 × 64 位 =64 字节
DDR516n每条分成两个 32 位子通道,BL16 × 32 位 =64 字节

64 字节正好是 CPU 缓存行的大小。这不是巧合:CPU 每次缺失缓存,就向内存要一整行 64 字节,内存一次突发刚好送完。所以哪怕你只读一个int,实际搬运的也是 64 字节。


六、多 Bank 并行:把慢操作藏起来

一个 Bank 在做 PRE 和 ACT 时,其他 Bank 可以同时工作。内存控制器会交错发命令:

时间 → Bank 0: [ACT]────[READ]────[PRE] Bank 1: [ACT]────[READ]────[PRE] Bank 2: [ACT]────[READ]────[PRE] 数据总线: ███ ███ ███ ← 几乎一直在传数据

单个 Bank 很慢,但多个 Bank 轮流上,数据总线就能保持忙碌。这和网络里"一条连接跑不满带宽,就开多条并发"是同一个思路。

地址映射:物理地址怎么拆

内存控制器把物理地址的各个位拆给不同层级(具体映射因平台而异,下面只是示意):

物理地址:│ 行 │ Rank │ Bank │ 通道 │ 列 │ 缓存行内偏移 │ 高位 低位

设计思路是:连续的地址先在同一行内的列上推进,再分散到不同的通道和 Bank。于是顺序访问一大块内存时,既能享受行命中,又能利用多通道、多 Bank 并行。


七、刷新:仓库的日常维护

上一篇讲过电容会漏电。具体怎么刷新:

参数DDR4 典型值含义
刷新周期64 ms(高温时 32 ms)所有行都要在这段时间内刷新一遍
REF 命令数8192 次 / 64ms把工作分成 8192 小份
tREFI约 7.8 μs平均每隔这么久发一次 REF
tRFC几百纳秒(容量越大越长)每次刷新期间,该 Rank 不能被访问
时间 → ──正常访问──[REF]──正常访问──[REF]──正常访问── ↑ 几百 ns 不可用 ↑ 每 7.8μs 来一次

估算一下代价:几百纳秒 ÷ 7.8 微秒 ≈百分之几的时间花在刷新上。芯片容量越大,要刷新的行越多,这个比例越高,所以新标准一直在改进刷新方式(例如 DDR5 支持按 Bank 刷新,刷新一个 Bank 时其他 Bank 照常工作)。

温度越高漏电越快,所以超过 85°C 时刷新频率要加倍 —— 这也是"内存过热会变慢甚至出错"的原因之一。


八、内存控制器:仓库的调度员

现代 CPU 内部集成了内存控制器,它负责:

工作说明
翻译地址物理地址 → 通道 / Rank / Bank / 行 / 列
发命令ACT、READ、WRITE、PRE、REF,并严格遵守所有时序参数
请求重排优先处理行命中的请求(常见策略 FR-FCFS:先服务行命中,再按到达顺序)
行策略开页:读完先不关,赌下一次还是这行;闭页:读完立刻关,减少冲突代价
安排刷新在合适的时机插入 REF
纠错支持 ECC 时负责检查和纠正错误

请求重排的思路,和前面网络负载篇的"带宽预算 + 优先级"很像:资源有限,先做代价最小、收益最大的事,同时防止某些请求被饿死。


九、各种 DRAM 家族

原理都是"1 个晶体管 + 1 个电容",区别在于接口和封装的取舍:

类型用在哪特点
DDR4 / DDR5台式机、服务器内存条容量大、通用;DDR5 把稳压芯片(PMIC)放到内存条上,芯片内部带 On-die ECC
LPDDR4X / LPDDR5手机、轻薄本低电压、低功耗,通常焊在主板上或与芯片封装在一起
GDDR6 / GDDR6X显卡显存位宽大、频率高,追求带宽,功耗和成本较高
HBM高端 GPU、AI 加速卡多层芯片垂直堆叠,接口位宽上千位,带宽极高

注意 DDR5 的 On-die ECC 只在芯片内部纠错,用于弥补工艺缩小带来的可靠性下降,不等于服务器上那种完整的 ECC 内存。


十、和游戏性能的关系:为什么"内存访问模式"这么重要

一组数量级

访问位置大约耗时换算成 4GHz CPU 周期
寄存器< 1 ns1
L1 缓存~1 ns~4
L2 缓存~3~5 ns~15
L3 缓存~10~20 ns~50
DRAM~60~100 ns~300

一次缓存缺失去访问 DRAM,CPU 可能要空等几百个周期。这几百个周期里本来能做上千次加法。

好的访问模式 vs 坏的访问模式

模式效果
顺序遍历数组每 64 字节缺失一次,后面全命中;CPU 硬件预取器还能提前拉数据;DRAM 端多是行命中
随机跳着访问几乎每次都缺失缓存,DRAM 端频繁行冲突
链表、指针满天飞的对象图下一个地址要等上一个读完才知道,无法预取,延迟一次次串行叠加

动手验证:顺序 vs 随机

usingSystem;usingSystem.Diagnostics;classMemoryAccessTest{staticvoidMain(){constintN=1<<25;// 3200 多万个 int,约 128MB,远大于 L3vardata=newint[N];varseq=newint[N];varrnd=newint[N];for(inti=0;i<N;i++){data[i]=i;seq[i]=i;rnd[i]=i;}// 打乱索引(Fisher-Yates)varr=newRandom(42);for(inti=N-1;i>0;i--){intj=r.Next(i+1);(rnd[i],rnd[j])=(rnd[j],rnd[i]);}Console.WriteLine($"顺序访问:{Measure(data,seq)}ms");Console.WriteLine($"随机访问:{Measure(data,rnd)}ms");}staticlongMeasure(int[]data,int[]index){varsw=Stopwatch.StartNew();longsum=0;for(inti=0;i<index.Length;i++)sum+=data[index[i]];// 访问的元素数量完全相同sw.Stop();if(sum==42)Console.WriteLine();// 防止被编译器优化掉returnsw.ElapsedMilliseconds;}}

两次循环做的加法一样多,读的元素也一样多,但随机访问通常会慢数倍甚至十几倍(具体倍数取决于 CPU、内存和缓存大小)。差距几乎全部来自缓存缺失和 DRAM 的行冲突。

在 Unity 里的体现

做法为什么快 / 慢
✅数组 /NativeArray连续存放数据顺序遍历,缓存和 DRAM 都友好
✅SoA(结构体数组拆成多个数组)只更新位置时,只读位置数组,不把无关字段搬进缓存
✅ECS / DOTS同类组件连续排列,本质就是为内存访问模式设计的架构
✅ 对象池复用连续内存减少碎片和 GC(前面对象池那一篇)
❌ 每帧遍历大量GameObject再GetComponent对象分散在堆里,到处跳着读
❌ 用LinkedList、大量class小对象指针追逐,无法预取
AoS(常见写法): [位置|血量|名字|AI状态][位置|血量|名字|AI状态]... 只想更新位置,却把整个对象都搬进缓存 SoA: 位置: [位置][位置][位置][位置]... ← 每 64 字节都是有用数据 血量: [血量][血量][血量][血量]...

前面讲网络时说"往返次数比数据量更关键";内存也是一样:访问 DRAM 的次数和方式,往往比计算量更决定性能。


十一、常见的误解

误解实际情况
“频率越高,延迟越低”频率主要提升带宽;以纳秒计的延迟变化不大,要同时看 CL
“读一个字节就只传一个字节”最少传一整个缓存行(64 字节)
“内存访问时间是固定的”行命中、行空闲、行冲突差好几倍,还受刷新和排队影响
“内存是随机访问的,所以顺序无所谓”"随机访问"指能访问任意地址,不代表任意顺序都一样快
“插满内存条一定更快”必须按主板说明组成双通道才有带宽加成;Rank 多了有时还得降频
“DDR5 自带 ECC,等于服务器 ECC 内存”On-die ECC 只保护芯片内部,传输路径上的错误它管不到
“内存不够就加,性能问题就解决了”容量解决的是"装不下";访问模式不好,再大的内存也一样慢

一句话总结

DRAM = 海量电容按"通道 → Rank → Bank → 行 → 列"组织起来的仓库。读取要先激活一整行到行缓冲区,再读出需要的列,换行前还要预充电关闭旧行;电容会漏电,所以每 64ms 要把所有行刷新一遍。
它的性能特征可以概括为三句话:带宽很大,延迟很高,同一行里连续访问最快。CPU 用缓存、预取和多 Bank 并行来掩盖延迟,而程序员能做的,就是让数据连续存放、顺序访问。


RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进