DDR 的带宽怎么算?介绍 DDR 的简化性能模型
因为 DDR 建模十分常用,却又蛮复杂的很多概念都经常忘记,整理一个笔记记录一下。
一言以蔽之,DDR 是一个 状态有关、高度并发、并带有动态性 的模型。
- 状态有关: DDR 的状态相关来自于短周期窗口内的地址访问规律,DDR 的访问效果不满足交换律。所以这里存在对相同的访问请求变更顺序和时间油画的空间,DDR 并不会主要做这件事情,片内的 memory controller/ 合并访存 承担了一部分将原始地址访问调整为 DDR 更为友好顺序的功能;
- 高度并发: DDR 内部存在许多空间上的并发架构,之间会产生可以并行关系,比如 bank parallism、burst read 打满流水线;也存在竞争关系,比如 bank conflict、row miss 、命令流和数据总线占用;
- 随机动态性: 主要来自 DDR 的刷新特性。时间参数取决于工艺和温度。动态性主要来自于两方面,一是温控,超过 85℃刷新速率翻倍;二是 DDR5 加入的 row hammer 防护,通过 count ACT 命令触发阈值自动触发刷新。如果建模足够细也可以当作确定性模型,但或许是高度状态相关导致确定性模型计算量爆炸,一般当作非确定性模型处理?
DDR 的架构层次和数据粒度
常见的 DRAM 主要以下两种,用金手指插条的 DIMM 和直接焊在芯片外面一圈的 GDDR。

以下是 DIMM 的结构图,层次上,自底向顶分别是 Row-Bank-Chip-Rank-DIMM, 数据接口上,宽度称为 channel width,但 channel width 物理上暴露出来的数据传输宽度,考虑 DDR 性能模型,还有 burst width 、 row buffer capacity 等。对于 DIMM 芯片内外,保持流平衡公式 \(Freq\times Width = Const\) ,IO 频率更高而内部 DDR 内部频率更低,所以在外部的宽度(channel width)较低(32bit/64bit 等),所以在内部的宽度(burst width)更高(8BL 代表一次读写8拍)。而 row buffer 的容量会比 burst width 更大,比如可以容纳 16 burst width 。如果是 GDDR 则 rank 之间不是走共享数据总线,而是分布式多个 channel ,channel 的粒度更细一点。

命令接口上,DDR 的命令并不是 read / write 两种原语,而是包括读写在内的:PRE、ACT、READ、WRITE 四种基础原语,每个原语都带有具体的空间坐标,比如具体哪个 bank 、哪个 row、哪个地址。READ 和 WRITE 一次指令交互的粒度是 burst width ,虽然可以通过配置寄存器切换 busrt width 长度,但 burst width 的宽度是由于内部的空间并行度,实际物理交互只是将对应阵列置无效并不能提高速度。
DDR4 8B channel width,BL8 = 64B,对应 BF16(2B) 数据精度相当于 32 个连续排列的数据,常见数据地址序一般将 channel 维度放在低地址,如果不是到 channel-wise 粒度稀疏一般都可以满足。换句话说,系统的最小友好粒度为各个组件最小友好粒度的最大值,而 channel-wise 粒度稀疏对 DDR 访存并不友好。
DDR 时序参数
DDR 内部有很多时间参数,完整 JEDEC DDR4 有三四十个时序参数,如下是 Ramulator 2[1] 中 DDR4 20 个时序建模参数(取值为 DDR4-2400R 预设的周期数,1 cyc = 0.833 ns):
| 族 | 参数 | 2400R 取值 |
|---|---|---|
| 单访存延迟 | tCL(CAS 列读延迟) | 16 |
| 单访存延迟 | tRCD(行激活到列访问延迟) | 16 |
| 单访存延迟 | tRP(预充电延迟) | 16 |
| 单访存延迟 | tRAS(行最小开启时间) | 39 |
| 单访存延迟 | tRC(行周期,同 bank 两次 ACT 最小间隔) | 55 |
| 单访存延迟 | tRTP(读到预充电间隔) | 9 |
| 总线占用/转向 | nBL(突发长度占用总线周期数,BL8/2) | 4 |
| 总线占用/转向 | tCCD_S(列到列延迟,不同 bank group) | 4 |
| 总线占用/转向 | tCCD_L(列到列延迟,同 bank group) | 6 |
| 总线占用/转向 | tCWL(CAS 写延迟) | 12 |
| 总线占用/转向 | tWR(写恢复时间) | 18 |
| 总线占用/转向 | tWTR_S(写到读转向,不同 bank group) | 3 |
| 总线占用/转向 | tWTR_L(写到读转向,同 bank group) | 9 |
| 激活限流 | tRRD_S(行激活到行激活,不同 bank group) | 4 |
| 激活限流 | tRRD_L(行激活到行激活,同 bank group) | 6 |
| 激活限流 | tFAW(四激活窗口,窗口内最多 4 次 ACT) | 26 |
| 刷新 | tREFI(刷新间隔,7.8 µs) | 9361 |
| 刷新 | tRFC(一次全 bank 刷新占用时间,360 ns) | 433 |
| 其它 | tCS(片选建立时间) | 2 |
模式切换相关参数包括(JEDEC 有、Ramulator 未建模)tXP/tXSR(断电进出)、tCKE、tMOD/tMRD(模式寄存器)、tZQCS/tZQinit(阻抗校准)、tDLLK(DLL 锁定)等。
在不同的地址请求下 DDR 延时建模不一致,比如如果 row miss rate 太高,每次访问都需要重新激活 ACT 指令,此时会受激活限流 tFAW 限制。在一般场景下,最关键的主要由 tRP / tRCD / tCL 三个决定,并且基本在所有协议中,这三个时间参数都几乎相等。在时间上,其主要反映两个因素,row buffer 的变化时间以及多级流水线的延时;在空间上,ACT / PRE / READ / WRITE 的粒度可以看作对于 bank 而言,对于多个 bank 可以做到并发掩盖延时。
这里似乎存在一个聚类工作的可能性,提出一种对地址流请求聚类的方法,分类到不同的解析模型和参数 bottleneck。可以调研常用的 DDR benchmark 是怎么设置的。
Intra-bank 掩盖: Row Buffer 的默认电位是 1/2 VDD,从默认电位到特殊电位有两个状态过程,“默认->特殊”(ACT,tRCD),以及“特殊->默认”(PRE, tRP)。对于同一个 row 的相邻访问。

掩盖 tCL: 每次读写完之后经过 IO 等的 tCL 时间(READ/WRITE,tCL),这来自接口多级流水延时,如果提供足够多的并行 (burst read/write) ,则可以掩盖这个延时,由于 read /write 分析默认以 burst width 交互粒度, 。
Inter-bank 掩盖 tRP/tRCD: 以及为了掩盖 tRP/tRCD ,多个 bank 的达成流水轮流使用总线,掩盖激活时间。
性能上下限解析模型
前面介绍了建模,这里补充具体实验展现不同地址请求对性能的影响,以 ramulator2 DDR4 2400 作为 baseline。首先定一下上下限,DDR 的带宽位于完全串行延时(串行)到完全并行延时(并行)之间,访存地址顺序决定了时空掩盖延时关系,在这之间进行取值。
\(B=\frac{Burst \times f}{n}=\frac{B_{ideal}}{n}=\frac{64B\times 1.2GHz}{n}=\frac{76.8GB/s}{n}\) ,其中 \(Burst\) 是一次 burst 交互的长度,BL8 代表一次 burst 交互长度为 8,而 channel width 是 64bit=8B,所以一次 burst 交互数据宽度是 64B;\(n\) 代表一次交互需要的周期数,我们建模地址请求主要影响该参数。
在不同场景下各个访存
上限,每次读取都可以隐藏延时(bank interleave+ row hit),只受到总线占用的限制,因此:
下限,准确的解析下限很难定义给出,这里给出几种下限定义:
- bank interleave + row conflict;
- bank conflict + row hit;
- bank conflict + row conflict。
case 1:bank interleave + row conflict。 这里假设每个请求都是 row conflict ,则受到 ACT 限流 tRRD_L, tFAW 限制,因此:
定义带宽利用率为实际带宽与最高带宽的比值, \(Util=\frac{nBL}{n_{max}}=\frac{4}{6.5}\approx 62.5\%\) ,row conflict 情况能够维持大概 6 成带宽,实际由于刷新等其他非理想因素可能会比这个更低。
case 2:bank conflict + row hit。 所有请求钉在同一个 bank、但都命中已打开的行:不需要再付 ACT/PRE,唯一约束是同 bank(必然同 bank group)连续两次读的最小间隔 tCCD_L,因此:
case 3:bank conflict + row conflict。 同一个 bank 且每个请求都换行:PRE、ACT、RD 在同一 bank 上完全串行,每个请求付一整个行周期 tRC,因此:
Ramulator 2 不同 DDR 配置的解析上限与下限利用率如下。DDR5 的 BL16 让单次请求占总线 8 cyc,而 tFAW/4 = 8 恰好允许同样的激活速率,激活限流与总线占用打成平手,纯 random 流理论上压不垮 DDR5;LPDDR5/HBM2 的突发短(nBL=2),激活限流相对更紧,下限只有 5 成左右。看起来利用率上下限优化和具体协议非常相关,这些参数和什么设计因素有关,以及具体实践和理论是否一致还需要进一步分析调研。
| 配置 | 通道数 | 单通道峰值 (GB/s) | 总峰值 (GB/s) | nBL | n_max | 下限利用率 (nBL/n_max) |
|---|---|---|---|---|---|---|
| DDR4-2400 | 1 | 19.2 | 19.2 | 4 | 6.50 | 61.5% |
| DDR4-2400 | 4 | 19.2 | 76.8 | 4 | 6.50 | 61.5% |
| DDR5-3200 | 1 | 25.6 | 25.6 | 8 | 8.00 | 100.0% |
| LPDDR5-6400 | 1 | 12.8 | 12.8 | 2 | 4.00 | 50.0% |
| HBM2-2Gbps | 4 | 32.0 | 128.0 | 2 | 3.75 | 53.3% |
上下限何时取到?
在 ramulator2 DDR4 下,地址从低到高按 Channel → Column → Bank → Row 切分顺序映射(RoBaRaCoCh),生成 8192 个请求,根据生成模式(顺序生成/随机生成/随机生成但单调),生成的地址范围(8192 个请求,覆盖 512KiB 地址空间100%,覆盖 256MiB 空间 0.195%)以及是否刷新,不同实验结果如下。这一套实验配置相当于 bypass memory controller 直接操作 DDR 观察地址请求设置对带宽的影响。如前文,row 命中情况存在 row hit/ row miss/ row conflict 三种情况,所以 row hit + row conflict 不等于 1。bank_ 前缀的实验把地址限制在同一个 bank 内(该 bank 的地址是每 128KiB 重复一段 8KiB 的不连续碎片),用于测量 bank conflict 场景。每个指标格内为 default/norefresh 两个值。
| 实验名称 | 想说明什么 | 生成模式 | 地址范围 | 发射顺序 | cyc/req | GB/s | 利用率 | row hit | row conflict |
|---|---|---|---|---|---|---|---|---|---|
| seq | row hit + bank interleave 的顺序流(上限参照,实测受 tCCD_L 限制) | sequential | 连续 | 顺序 | 5.276 / 5.111 | 14.56 / 15.03 | 75.8% / 78.3% | 0.991 / 0.992 | 0.000 / 0.006 |
| random_256MiB | case 1:bank interleave + row conflict(多 bank 解析下限) | random | 256 MiB | 随机 | 6.760 / 6.468 | 11.36 / 11.87 | 59.2% / 61.8% | 0.001 / 0.001 | 0.993 / 0.997 |
| random_256MiB_sorted | 大范围下排序:单调不等于局部性(间距 32KiB ≫ 行) | random | 256 MiB | 排序 | 6.094 / 5.801 | 12.60 / 13.24 | 65.6% / 69.0% | 0.107 / 0.107 | 0.884 / 0.888 |
| random_512KiB | 稠密随机铺满 bank group(tCCD_S),实测最高利用率 | random | 512 KiB | 随机 | 4.474 / 4.302 | 17.16 / 17.85 | 89.4% / 93.0% | 0.342 / 0.342 | 0.616 / 0.617 |
| random_512KiB_sorted | 稠密排序 ≡ 顺序流(对照组,与 seq 逐值相等) | random | 512 KiB | 排序 | 5.276 / 5.111 | 14.56 / 15.03 | 75.8% / 78.3% | 0.991 / 0.992 | 0.000 / 0.006 |
| bank_random_256MiB | case 3:bank conflict + row conflict(真正下限,≈ tRC=55 串行) | random | 256 MiB 单 bank | 随机 | 56.482 / 53.892 | 1.36 / 1.43 | 7.1% / 7.4% | 0.016 / 0.016 | 0.980 / 0.984 |
| bank_random_256MiB_sorted | 单 bank 下排序把 ~4 个请求攒进同一行,摊薄 tRC(3.5×) | random | 256 MiB 单 bank | 排序 | 15.924 / 15.180 | 4.82 / 5.06 | 25.1% / 26.3% | 0.753 / 0.755 | 0.245 / 0.245 |
| bank_random_512KiB | bank conflict + 高命中:bank 间并行 > 行局部性的反例 | random | 512 KiB 单 bank | 随机 | 7.929 / 7.601 | 9.69 / 10.10 | 50.4% / 52.6% | 0.953 / 0.953 | 0.046 / 0.047 |
| bank_random_512KiB_sorted | case 2:bank conflict + row hit(tCCD_L 界 n=6) | random | 512 KiB 单 bank | 排序 | 6.276 / 5.990 | 12.24 / 12.82 | 63.7% / 66.8% | 0.999 / 1.000 | 0.000 / 0.000 |
可以看到顺序读写(~75%)并不一定是利用率最高,最高利用率在 随机+小范围地址空间内达到 (90%),随机单调+小范围利用率退化也证明了这一点(75.8%)。这是因为顺序读写虽然都是 row hit (row hit ~99%),但受到同 bank 制约(tCCD_L),而随机+小范围地址访问则受 tCCD_S 影响,虽然现在的 RoBaRaCoCh 映射方案顺序访问是优先填满 row 然后切换 bank 也实现了 bank interleave (row hit 优先级> bank interleave 优先级),但将 bank 映射到更低地址利用率会更高(bank interleave 优先级 > row hit 优先级),这个由 tCCD_L 和 tCCD_S 的大小关系决定。但无论怎样 利用率上限在 row hit + bank interleave 的情况下达到,具体实现可以通过静态地址映射方案+动态 MC 调度实现。而多 bank 下的利用率下限在随机+大范围 case 出现,不考虑刷新约为 60%,和前面解析下限近似略低;若进一步把地址囚禁到单个 bank(bank_ 组),下限下探到 7.3%(≈ tRC 串行),与解析 case 3 一致。

浙公网安备 33010602011771号