多通道闪存并发下DDR带宽的建模与估算方法
做存储控制器或者SSD主控的工程师应该都躲不开一个灵魂拷问DDR带宽到底配多大才够配小了高负载下性能断崖式下跌配大了芯片面积、成本、功耗全在跟你作对。这个系列写到第三篇前两篇我们分别聊过CPU侧和DMA侧怎么估算带宽需求这一次把矛头对准闪存侧——尤其是当闪存通道多起来之后那些看似独立的并发访问最后汇聚到DDR接口上造成的聚合压力究竟有多大。先说结论绝大多数人拍脑袋定的DDR位宽和频率其实都建立在“单通道、顺序读写”的理想假设上。一旦闪存进入多通道并发模式特别是多个通道同时完成页面读取、后台垃圾回收GC又在抢带宽时DDR的瞬时需求往往是稳态吞吐的好几倍。如果不把这个瞬时压力建出来等流片回来再发现瓶颈代价就太大了。这篇文章我会从闪存访问的物理路径开始把多通道并发的流量特征拆开然后给出一个可落地的带宽建模方法最后附上我在FPGA验证和实测中踩过的坑希望给正在做控制器选型的朋友一些参考。1. 多通道闪存并发到底在并发什么1.1 闪存访问的“页面经济”page buffer与DDR最小事务要理解聚合压力先得搞清楚一次闪存访问在数据通路上到底发生了什么。NAND Flash的最小读取和编程单位是页Page常见的页大小是16KB也有8KB或者16KB冗余区的配置。每一颗NAND die内部都有一个SRAM区域叫page buffer它位于CMOS电路层本质上是若干组锁存器大小至少能容纳一页数据。控制器读闪存时NAND先把存储阵列里的一页数据搬运到page buffer这个过程耗时是微秒级通常在50到100微秒之间然后控制器再把page buffer里的数据通过IO总线搬出来这个搬运速度是纳秒级最快可达800MT/s甚至更高。关键点在这里page buffer决定了数据是以“整页”为单位进出NAND的所以控制器和DDR之间打交道的最小数据事务不是几个字节而是一整页。当你只有单个通道、单颗die工作时16KB的数据搬移在DDR看来也就是16KB的流量算起来很简单。可是当系统里有8个通道、每个通道4个CE、每个CE又支持多平面并发时同一时刻可能有多颗die同时把page buffer准备好就等着控制器来取数。这一瞬间DDR要面对的不是16KB而是几百KB的集中数据涌出。1.2 通道、CE、Plane三级并行带来的瞬时带宽冲击多数现代闪存控制器都支持三级并行通道Channel并行、CEChip Enable并行、以及Die内部的Plane并行。通道并行就是多个独立的闪存接口同时工作CE并行是同一个通道上挂多颗die通过片选信号分时访问Plane并行则是单颗die内部的多个存储平面同时操作比如4-Plane的die一次可以读取4个页也就是64KB的数据同时进入page buffer。这三层并发叠加起来瞬时带宽的放大效应非常可观。举个例子8通道设计每个通道1个CE、4-Plane一次多平面读操作就会要求从8个通道同时搬出数据每通道64KB合计512KB。假设DDR数据宽度是32bit、频率2400MT/s理论带宽是9.6GB/s搬完这512KB约需53微秒。而NAND读数据准备好只花了70微秒左右等于说NAND每准备好一批数据DDR就要用53微秒去“消化”剩余时间窗口非常有限一旦还有GC、主机读、FTL表项访问同时插进来DDR就变成了真正的瓶颈。1.3 顺序读、随机读、GC搬移三种流量画像完全不同做带宽建模时最忌讳把所有流量当成同一种类型。我习惯把闪存侧的DDR访问分成三类典型的流量画像。第一种是顺序读主控提前下发大批量读命令各通道的die几乎会在同一时间段完成数据准备DDR端看到的是周期性的大脉冲脉冲宽度取决于页面大小和搬运效率。第二种是随机读各die数据准备好的时间点天然错开DDR端看到的是低占空比但频繁出现的小块流量对调度器的考验更大。第三种是垃圾回收GC搬移它包含读旧block和写新block两个方向等于把用户数据在DDR里“过了一遍”流量翻倍而且GC往往会在后台批量执行加剧瞬时冲突。这三类画像不能只算平均带宽必须算它们在时间轴上的重叠程度。因为DDR是一个共享资源所有流量最终都汇聚到同一个接口上瞬时请求速率一旦超过带宽数据就只能排队排队时间一长闪存侧就饿死了性能直接拉垮。2. 聚合压力从哪里来三条流量把DDR当成了什么2.1 读搬移把page buffer“倒”给主机的一次性脉冲读路径上的DDR压力最直观。无论是主机发起读请求还是FTL为了GC先读出旧数据步骤都是闪存先准备好page数据然后控制器通过DMA把page buffer内容搬到DDR再由主机接口从DDR取走。这里经常被低估的是“突发性”。NAND的读延迟很长比如典型NAND tR是70微秒但数据真正进入page buffer之后IO传输只要几微秒。也就是说每70微秒左右每个通道会释放一个“数据脉冲”。如果8个通道的读命令在下发时没有做很好的相位错开那么这些脉冲会叠加到几乎同一个时刻DDR的瞬时利用率瞬间冲到90%以上。这就是为什么有时候你测顺序读的平均DDR占用率只有40%可一旦开启多通道并发系统还是会出现卡顿——平均没用要盯着峰值窗口算。2.2 编程与编程抑制写方向流量容易被低估写方向的流量比读方向更容易算漏。以TLC NAND为例编程时数据先要从DDR搬入寄存器再写入page buffer但这只是第一层。真正隐蔽的是“编程抑制”机制带来的附加值。NAND编程时为了让未选中的page不被误写入DIE内部会在bitline上施加电压或者通过沟道自举channel boosting来抑制编程这个过程本身不消耗DDR带宽但它影响了你在DDR侧看到的写事务形态。有些主控为了做部分编程partial programming或者支持编程抑制下的数据隔离会先读旧页、合并新数据再写回新页。这个读改写Read-Modify-Write操作让写放大系数直接变成2甚至更高。也就是说应用层写1GB数据DDR实际要承担2GB以上的读写混合流量。我在实际项目里见过有人只按“1倍的写流量”估算结果GC一开DDR利用率直接飙到95%以上无论怎么调调度器都压不下来最后只能牺牲GC节流来保主机性能这是典型的模型漏项。2.3 表项、FTL、缓存回写边角流量也能压垮骆驼除了用户数据的读写闪存控制器还需要访问DDR里的FTL映射表、坏块表、日志区以及RAID校验数据。这部分流量单个看很小但频率极高。比如4KB映射粒度下每搬一次4KB用户数据就可能伴随一次映射表读和一次映射表写这就额外产生了8KB左右的DDR流量。在随机小IO场景下表项流量的占比经常能到30%以上。还有一个容易被忽略的是缓存回写。很多控制器会利用DDR做写缓存把小的主机写请求先攒着凑满一个闪存页再写入。这个设计本身是为了减少写放大但它会引入“延迟写”流量——即主机空闲时后台把缓存数据刷进闪存同时仍然要从DDR读出校验数据。如果缓存命中率不高这部分流量还会进一步放大。所以建模时不能只盯着稳态数据面要把FTL表项、缓存管理、磨损均衡带入的整体方案都算进去。3. 带宽模型怎么建占用率公式、关键参数与完整算例3.1 用时间窗口占用率替代“峰值带宽”的建模框架我建模时不太倾向于直接算“瞬时峰值带宽”因为瞬时峰值往往只能维持一两个时钟周期实际调度器根本追不上算出来容易把人吓到也不符合物理事实。更实用的做法是算“时间窗口占用率”选一个合适的时间窗口比如100微秒或者1毫秒统计窗口内所有DDR访问请求的总传输字节数加上刷新、读写切换等协议开销再除以窗口时长和DDR理论带宽得到这个窗口的带宽占用率。公式可以写为[ U_{DDR} \frac{\sum_{i} (V_{read,i} V_{write,i}) V_{overhead}}{T \times B_{DDR}} ]其中 (V_{read,i}) 和 (V_{write,i}) 是窗口内各类流量源的数据量(V_{overhead}) 是刷新与读写切换开销对应的等效数据量(T) 是观察窗口长度(B_{DDR}) 是DDR理论带宽。如果 (U_{DDR}) 超过70%就要高度警惕。因为DDR控制器调度效率很难达到100%多bank并行度不足、读写冲突、刷新抢占都会让实际可用带宽打折70%已经是我按多年经验给出的保守警戒线。3.2 关键时序参数tWTR、tREFI与刷新惩罚建这个模型光有数据量还不够还必须把DDR协议层的开销算进去不然算出来会偏乐观。这里我重点说两个参数tWTR和tREFI。tWTR是DDR写转读的最小间隔时间DDR4-2400通常在7.5纳秒到10纳秒左右。如果DDR控制器频繁在写和读之间切换每次切换都要付出这个间隔代价。换算成等效带宽损失假设每次切换损失10纳秒而一次BL8突发在32bit位宽下传输32字节只需要约3.3纳秒那么一旦读写比例是1:1且没有足够重排序空间切换开销甚至可能占到总时间的60%以上。实际控制器里会通过读写重排序来合并方向但能够合并的深度取决于pending队列大小这一点在后文会展开。tREFI是刷新间隔DDR4的典型值是7.8微秒即每7.8微秒需要发起一次刷新操作。每次刷新命令占用tRFC时间通常为350纳秒左右。刷新带来的带宽惩罚大约是 (tRFC / tREFI)算下来约4.5%。这个比例在32bit窄接口上折算成带宽就是 9.6GB/s × 4.5% ≈ 430MB/s已经相当于一个NAND通道的持续吞吐量了。如果系统用了非常多的rank或者开启了刷新密集型模式这个比例还会更大。3.3 手算实例8通道闪存加32bit DDR4-2400够不够用拿一个我近期评估过的配置来举例这样更有实感。系统包含8个闪存通道每通道挂1个CE每CE支持4-Plane并发页面大小16KB因此一次多平面读最多产生64KB数据。DDR配置为32bit位宽、DDR4-2400理论带宽9.6GB/s。先看纯主机顺序读场景。每个通道触发64KB数据搬移8通道合计512KB。NAND数据准备时间按70微秒估算那么单轮数据搬移的DDR占用时间 (t_{dma} 512KB / 9.6GB/s \approx 53微秒)。在一个含读命令下发时间的周期内假设90微秒DDR读占用率约 (53 / 90 \approx 59%)。加上刷新4.5%的损失以及少量读写切换开销整体占用率接近65%仍可接受。再看GC叠加场景。假设此时后台有4个通道在做GC搬移每个通道每轮搬64KB旧数据出来、再搬64KB新数据进去同时主机侧另外4个通道每轮搬64KB读数据。窗口内总流量 4通道GC读256KB 4通道GC写256KB 4通道主机读256KB 768KB。DDR占用时间约80微秒。如果窗口仍是90微秒占用率已经接近90%。这个数字已经越过危险线说明这套DDR配置在该混合负载下余量不足要么提高频率到DDR4-2666要么把位宽扩到64bit要么必须限制GC并发通道数。建模到这里选型结论就非常清楚了不再靠感觉拍板。3.4 AXI Outstanding与DDR调度器对模型的影响公式算完只能回答“带宽够不够”回答不了“调度器能不能把带宽用起来”。这里必须引入AXI总线的outstanding能力。控制器内部通常通过AXI接口连接DDR控制器AXI的outstanding事务数量决定了系统能把多少个未完成请求同时压在DDR控制器里。如果outstanding太少DDR控制器的重排序窗口就小读写合并和bank调度都施展不开。做个粗算假设DDR读延迟从收到读请求到数据返回是100纳秒一次AXI burst传输64字节。如果outstanding只有4那么理想情况下总线上的在飞数据只有 (4 \times 64B 256B)吞吐上限约 (256B / 100ns 2.56GB/s)远低于9.6GB/s的理论带宽。如果把outstanding提升到32在飞数据变成2KB吞吐上限能到20GB/s带宽才不被outstanding卡住。所以建模时一定要同时校验AXI配置的outstanding深度以及DDR控制器内部的bank级并行度否则模型算出来再漂亮实际性能也到不了。4. 瓶颈排查与验证技巧实测对不上模型怎么办4.1 模型预测和实测对不上先查这三个地方模型建好了上了FPGA验证平台一测发现DDR利用率比模型预测高出一大截这种事我遇到过太多次。大多数情况下问题不在模型本身而在下面三个地方。第一查NAND命令下发间隔。假设NAND的tR是70微秒但控制器如果在下发读命令后没有及时准备下一批命令实际周期可能拉到90甚至100微秒。周期变长会让DDR占用率变低但吞吐也变低这时候不是模型错了而是测试用例没有跑满闪存侧的压力。第二查读改写流量。打开FTL的读改写日志看实际发生的RMW次数。如果RMW比例超过预期DDR写流量会显著增加这通常和部分编程策略、cache回写粒度有关。第三查刷新与读写切换的实际开销。用DDR控制器的性能计数器统计刷新次数和读写切换次数和理论值对比。如果切换次数远高于模型假设说明AXI outstanding不够调度器没有足够的空间去做读写方向合并。4.2 容易被忽略的四个坑刷新对齐、Bank Hash、小突发占空比、QoS排查完上面三处还有几个工程细节特别容易让人栽跟头。第一个坑是刷新对齐。DDR控制器通常会在多个bank之间轮转刷新如果刷新的时机恰好和闪存数据脉冲集中的时刻重叠那么本来只需要4.5%刷新开销的情况会变成某个10微秒窗口内DDR利用率陡增20%甚至更多。解决方法是尽量使用DDR控制器的“刷新调度”模式让刷新命令均匀散布在时间轴上而不是攒到某个边界统一触发。第二个坑是Bank Hash没有打开。很多DDR控制器的默认地址映射是“连续地址优先命中同一bank”这在小粒度随机访问时会导致所有请求扎堆到同一个bank groupbank级并行度严重不足实际带宽可能只有理论值的60%。确认你的DDR控制器开了bank/chip select的hash散列功能并且地址交织粒度与闪存页大小匹配比如16KB页面就尽量让地址每4KB或8KB跨越一个bank。第三个坑是小突发占空比。闪存侧DMA如果默认burst长度只有32B或64B而DDR带宽很高、延迟又大那么每次burst之间的空闲时间会把效率拖垮。我在实际项目中把DMA的burst长度从64B调到256B之后DDR有效带宽提升了超过20%这就是小突发占空比造成的损耗。第四个坑是QoS优先级配置。如果在多主控场景下比如CPU、闪存DMA、主机DMA都接DDR没有给实时性要求高的闪存DMA设置足够高的QoS一旦DDR拥塞闪存DMA会被CPU流量挤到队尾形成连锁延迟放大。建议把闪存DMA的优先级设为高同时给CPU侧请求设置超时回退机制避免CPU把带宽吃满。4.3 常见问题速查表现象排查方向处理建议DDR利用率高于模型预测20%以上FTL读写合并、GC策略是否激进打开RMW日志统计GC搬移与主机IO重叠比例顺序读吞吐上不去AXI outstanding深度不足、DMA burst过短增大outstanding到16以上burst长度设为128B/256B随机小IO下DDR延迟高Bank Hash未开启、读写切换频繁开启地址散列增大DDR控制器重排序窗口刷新时段出现毛刺卡顿刷新命令与数据脉冲重叠使能均匀刷新调度必要时调整tREFI自适应模式多通道并发时部分通道饥饿QoS优先级配置不当拉高闪存DMA优先级设置CPU侧超时回退模型算挂OK但实测总是差一点刷新开销按理论值算未考虑实际调度用控制器性能计数器实测刷新次数校正模型收尾一点实际体会最后分享一个我个人的习惯做DDR带宽建模时我通常会在估算结果上额外预留20%的余量再留给调度器效率损耗。因为无论模型多细致总有些边角流量是拍脑袋算不全的比如温度变化导致的刷新频率调整、NAND坏块后的地址重映射、固件升级时的特殊流程这些都不会出现在理想工况清单里。与其把设计卡在95%的理论利用率上不妨退一步留出余量稳定性带来的收益比省那一点DDR位宽成本划算得多。另外这个模型并不局限在闪存控制器场景凡是涉及到多个异步数据源汇聚到共享内存接口的系统比如网卡的多队列收包、视频编解码的多路取流都可以用同样的思路“识别数据脉冲、算窗口占用率、留调度余量”这三板斧是通用的。如果你们正在做的产品也遇到类似的DDR压力问题不妨先按这个方法建一版模型再拿FPGA实测数据去校准你会发现很多“玄学性能问题”其实都是可以提前用计算避免的。