内存控制器设计:请求调度策略

各位同学,今天我们来聊聊内存控制器里最核心的一块——请求调度策略。说白了,就是当一堆读写请求涌向DRAM时,控制器该怎么排队、怎么插队、怎么避免有人饿死。

我在做DDR4控制器项目时,就遇到过这么个情况:系统跑着跑着,某个低优先级的核死活拿不到数据,延迟飙到天上去了。后来一查,就是调度策略没处理好。嗯,这节课咱们就把这块掰开揉碎了讲。

FR-FCFS调度:先就绪,再老幼

FR-FCFS,全称是First-Ready First-Come First-Serve。名字挺长,但逻辑很简单——先服务那些已经准备好(行命中)的请求,再按到达顺序服务其他请求

你想想看,DRAM访问最耗时的操作是什么?是行激活(tRCD)和预充电(tRP)。如果请求恰好命中当前打开的行,那就能省掉这两步,延迟直接砍掉一大截。

核心思想: 行命中优先,同行的请求先走,不同行的按先来后到排队。

我在一个视频处理芯片的项目里,用FR-FCFS把内存带宽利用率从65%提到了82%。为什么?因为视频流的数据访问模式很规律,大量请求落在同一行上,FR-FCFS正好吃准了这个特点。

但FR-FCFS有个隐患——饥饿问题。如果一直有行命中的请求插队,那些行不命中的请求可能永远排不上队。我见过一个极端案例:某个核的请求延迟从正常的50ns飙到了2μs,就是因为被连续的行命中请求活活饿死了。

饥饿避免:给老请求一个插队权

怎么解决饥饿?说白了就是加个老化机制。每个请求在队列里待的时间越长,优先级就越高。当某个请求的等待时间超过阈值,它就能强行插队。

我常用的做法是两阶段优先级

  • 第一阶段: 按FR-FCFS正常调度
  • 第二阶段: 如果某个请求等待超过N个周期(比如64个周期),直接提升到最高优先级

代码实现起来其实不复杂,我贴一段伪代码给大家看看:

// 请求调度核心逻辑
if (存在老化请求) {
    // 老化请求优先
    选择等待时间最长的老化请求;
} else {
    // 正常FR-FCFS
    if (存在行命中请求) {
        选择最早到达的行命中请求;
    } else {
        选择最早到达的请求;
    }
}
我的经验: 老化阈值别设太小,否则FR-FCFS的优势就没了。我一般设64-128个周期,具体要看你的时钟频率和tRCD时间。

曾经有个项目,我把阈值设成了32,结果发现行命中率从80%掉到了55%,得不偿失。后来调到96,才找到平衡点。

Bank/Rank级并行度:别让DRAM闲着

DRAM内部有多个Bank,每个Bank可以独立工作。如果你只盯着一个Bank发请求,其他Bank全闲着,那带宽利用率肯定上不去。

我画了一张图,帮大家理解Bank级并行度的概念:

Bank级并行度示意图 Bank 0 行激活中 Bank 1 读数据中 Bank 2 预充电中 Bank 3 空闲 共享数据总线(忙) Bank 0~3 可并行执行不同操作 但共享数据总线一次只能传输一个Bank的数据 调度器需合理安排,避免总线冲突

看到没?Bank 0在激活行,Bank 1在读数据,Bank 2在预充电,Bank 3闲着。它们可以同时干活,但共享的数据总线一次只能传一个Bank的数据。调度器的任务就是让这些操作流水线起来,别让总线闲着

Rank级并行度也是类似的道理。一个DIMM上可能有多个Rank(比如双面内存就是两个Rank),每个Rank有独立的片选信号。你可以让Rank A在预充电,同时Rank B在读数据,这样带宽利用率又能往上提一截。

注意: Rank切换有额外开销(tRAS、tRP等),别切换太频繁。我一般建议至少连续发4-8个请求到同一个Rank,再切到另一个Rank。

实战中的调度策略选择

不同的应用场景,调度策略的侧重点完全不同。我整理了一个表格,方便大家对照:

应用场景 推荐策略 关键参数 我的经验
视频编解码 FR-FCFS + 弱老化 老化阈值128周期 行命中率可达85%以上
数据库/OLTP 公平轮转 + 强老化 老化阈值64周期 避免长尾延迟
AI推理 Bank级并行优先 Bank轮转粒度4请求 带宽利用率提升20%
实时系统 优先级固定 + 老化 高优先级无老化 确保实时任务延迟可控

我个人习惯在项目初期先用FR-FCFS + 中等老化(96周期)作为基线,然后根据实际负载特征微调。你想想看,如果一开始就搞复杂的调度算法,出了问题你都不知道是调度的问题还是别的问题。

避坑指南: 我曾经在一个项目里,为了追求极致带宽,把Bank级并行度开到最大,结果发现功耗超标了。因为同时激活太多Bank,电流峰值太大。后来加了Bank激活数量限制(最多同时激活4个Bank),才把功耗压下来。

最后说一句,调度策略没有银弹。你在仿真里跑得再漂亮,上了芯片可能又是另一回事。我的建议是:把调度参数做成可配置的,留几个寄存器给软件调。这样芯片回来后,还能根据实际负载做优化。


公众号:蓝海资料掘金营,微信deep3321