ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

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

DRAM 的工作原理:一座需要不停维护的巨型仓库 一、生活里的例子图书馆的密集书库一座大型图书馆书不放在阅览室而是放在后面的密集书库你要一本书管理员先走到对应的书库房间把一整排书架推出来密集书架平时是合拢的从这一排里取出你要的那本如果下一本书在同一排直接再拿就行很快如果在另一排要先把这排推回去再推出新的一排慢很多另外书会慢慢受潮管理员要定期翻晒图书馆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每条 12 个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 后等多久才能 READtRPRow 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 查完各级缓存、经过内存控制器排队的时间一次真正的内存访问通常在 60100 ns。一个常被误解的点内存CL换算成纳秒DDR4-2400 CL151512.5 nsDDR4-3200 CL161610 nsDDR5-6000 CL303010 nsDDR5 的 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存储阵列内部一次并行读出很多位再由接口快速地一个接一个送出去。代际预取一次突发BurstDDR48nBL8连续 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平均每隔这么久发一次 REFtRFC几百纳秒容量越大越长每次刷新期间该 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 ECCLPDDR4X / LPDDR5手机、轻薄本低电压、低功耗通常焊在主板上或与芯片封装在一起GDDR6 / GDDR6X显卡显存位宽大、频率高追求带宽功耗和成本较高HBM高端 GPU、AI 加速卡多层芯片垂直堆叠接口位宽上千位带宽极高注意 DDR5 的 On-die ECC 只在芯片内部纠错用于弥补工艺缩小带来的可靠性下降不等于服务器上那种完整的 ECC 内存。十、和游戏性能的关系为什么内存访问模式这么重要一组数量级访问位置大约耗时换算成 4GHz CPU 周期寄存器 1 ns1L1 缓存~1 ns~4L2 缓存~35 ns~15L3 缓存~1020 ns~50DRAM~60100 ns~300一次缓存缺失去访问 DRAMCPU 可能要空等几百个周期。这几百个周期里本来能做上千次加法。好的访问模式 vs 坏的访问模式模式效果顺序遍历数组每 64 字节缺失一次后面全命中CPU 硬件预取器还能提前拉数据DRAM 端多是行命中随机跳着访问几乎每次都缺失缓存DRAM 端频繁行冲突链表、指针满天飞的对象图下一个地址要等上一个读完才知道无法预取延迟一次次串行叠加动手验证顺序 vs 随机usingSystem;usingSystem.Diagnostics;classMemoryAccessTest{staticvoidMain(){constintN125;// 3200 多万个 int约 128MB远大于 L3vardatanewint[N];varseqnewint[N];varrndnewint[N];for(inti0;iN;i){data[i]i;seq[i]i;rnd[i]i;}// 打乱索引Fisher-YatesvarrnewRandom(42);for(intiN-1;i0;i--){intjr.Next(i1);(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){varswStopwatch.StartNew();longsum0;for(inti0;iindex.Length;i)sumdata[index[i]];// 访问的元素数量完全相同sw.Stop();if(sum42)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 并行来掩盖延迟而程序员能做的就是让数据连续存放、顺序访问。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表