Skip to content

算法Algorithm

模调度

Modulo scheduling · 周期性软件流水调度

以原迭代距离、固定启动间隔和模资源槽证明周期调度合法,再显式展开有限启动与排空,用数值前缀和及别名反例检验每次访存。

形式陈述 ​

给同一循环体安排重复的时间图案 ​

延迟感知的列表调度给一份有限操作图安排启动时间。循环还有另一种机会:第0轮的乘法在等待结果时,可以启动第1轮的加载。模调度为每种循环体操作选择一个偏移 s(v),再让第 i 轮的同一操作于 s(v)+iII 启动。正整数 II 称为启动间隔;它是相邻迭代时间图案的平移量,不是单轮完成所需时间。

本页沿用固定正整数延迟 L(v)、唯一资源类别 r(v)、每拍启动容量 cᵣ,以及完成先于同拍启动的约定。资源完全流水化,延迟不等于连续占用时间。所有偏移是非负整数。循环体是有限的固定操作集合,没有条件跳过、提前退出或可变延迟;运行前给定有限非负轮数 N。

输入边为 (u,v,d),其中 d 是循环依赖分析给出的原迭代距离:u 的第 i 轮实例必须完成,v 的第 i+d 轮实例才能启动。本页要求 d≥0,距离0的边构成 DAG;距离正的边可以构成循环。距离描述源循环中的实例关系,不能因为把指令移到别的时间槽就改写它。

每轮结果使用新鲜的虚拟名字,初始跨迭代值另外提供。模型不自动给出有限物理寄存器分配、旋转寄存器指令或汇编循环。纯计算使用数学整数、确定总原语;访存例子则明确证明所需顺序,没有异常、越界、volatile、并发修改或缓存失误。

一张周期表的两个可检查条件 ​

把生产者和消费者的实际时间代入,就得到每条边的约束

s(v)+dII ≥ s(u)+L(u).

这里消掉的是共同的 i·II,不是 d·II。若忘掉 d,所有真正递推都会被误看成同一轮的环;若擅自减小 d,则可能读到还没算好的上一轮结果。

对每种资源 r 和余数 q=0,…,II−1,还需

|{v:r(v)=r, s(v)modII=q}|≤cr.

这就是本页的模资源表。一种操作的各轮实例每隔 II 启动一次;两种操作即使偏移相差很多,只要余数相同,在足够靠后的某个时刻就会同时争用启动名额。因此只查第0轮的绝对时间是不够的。

这两个条件对从第0轮开始、无限持续重复的时间图案给出必要且充分的依赖与启动容量检查。对任何有限前缀,它们仍然充分;但某个很短的有限循环可能在冲突出现之前已经结束,所以不能说它们对每个固定小 N 都必要。本页搜索的是可重复的周期表,不是只为某个短 N 定做的最优表。

下界与诚实的搜索结果 ​

令 nᵣ 是一轮使用资源 r 的操作数。一个周期只有 II·cᵣ 个启动名额,所以

ResMII=maxr⌈nrcr⌉

是必要下界。对每个有向简单环 C,把边上的不等式相加,所有偏移相消,得到

II∑(u,v,d)∈Cd ≥ ∑(u,v,d)∈CL(u).

距离0子图无环,故分母严格为正。取所有简单环的延迟和与距离和之比,向上取整并取最大值,得到 RecMII;无环时此项取0。非简单闭合游走可以分解为简单环,其比值是这些环按距离和加权的平均,所以不会给出更强下界。候选 II 至少为 max(1,ResMII,RecMII)。这仍只是必要条件。

参考器固定一个候选 II 和偏移窗口 0≤s(v)≤H,按距离0拓扑次序逐节点尝试0到 H。每次试放后,检查已同时放置的边端点及已用模槽;违反就回退。所有节点放妥时再完整检查,才返回 feasible 与整张时间表。该实现是便于复算的小型有界搜索,不冒充文献中带撤销和重排启发式的完整迭代模调度器。

若 II 小于带证据的资源或环下界,返回 infeasible_by_bound,确实证明此 II 全局不可行。若穷尽窗口,返回 no_schedule_in_window,只证明这个 H 内没有表。若试放预算先用完,返回 search_limit,没有可行或不可行结论。后三种结果均不交付半成品时间表;扩大窗口或预算可能改变后两种结果。

直觉

可以把一轮当成一张透明纸,上面标着各操作的启动位置。每隔 II 拍叠上一张新纸。检查一张纸,只能知道一轮内部有没有冲突;把所有纸折叠到 II 个余数格里,才能看到不同轮之间的启动争用。

递推边是另一种限制。即使加法器每拍都能启动一个新加法,下一轮累加若必须读取上一轮的和,仍得等那个和真正完成。多放几个空闲端口不能缩短一条不可打断的数据递推。

例子与边界

四操作前缀和:为什么存储要从6移到7 ​

输入数组 X、整数因子 k 和初始累加值 a。输出数组 Y 与 X 完全不重叠,Y 的各下标也指向不同单元。源循环按 i=0,…,N−1 执行:

text
A_i: load X[i]             // 延迟2,MEM
B_i: b_i := k * A_i        // 延迟3,MUL
C_i: c_i := c_(i-1) + b_i  // 延迟1,ALU;c_(-1)=a
D_i: store Y[i] := c_i     // 延迟1,MEM

MEM、MUL、ALU 每拍容量都是1。A→B、B→C、C→D 的距离为0,C→C 的距离为1。X 只读,Y 的写入彼此不同且不会改变 X,所以没有遗漏的跨轮存储冲突;加载在启动时采样 X,存储在完成时写入 Y。所有加载、乘法、加法及存储都要实际执行一次。

单轮列表表为 s=(0,2,5,6),最后在7完成。资源下界为2,因为 MEM 每轮需要加载和存储各一次;累加自环给出递推下界1。因此先试 II=2。但照搬单轮偏移时,A 的余数0与 D 的余数0冲突:例如 D₀ 在6启动,A₃ 也在6启动。第一轮单看完全合法,重复后却超出 MEM 容量。

将 D 的偏移推迟为7,得到 s=(0,2,5,7)。MEM 的 A 占余数0、D 占余数1;MUL 的 B 占余数0,ALU 的 C 占余数1。距离0三条边的松弛量分别为0、0、1,C 自环的松弛量为 5+2−5−1=1,全部非负。于是每隔两拍可开始一个新图案,而一轮自身从加载到存储完成仍需8拍。

取 X=[1,2,3,4]、k=2、a=0。四轮的 A 启动于0、2、4、6;B 启动于2、4、6、8;C 启动于5、7、9、11;D 启动于7、9、11、13,分别在8、10、12、14写入 Y。C 的结果依次为2、6、12、20,所以最终 Y=[2,6,12,20]。t=7 可以同时启动 C₁ 与 D₀,因为它们用不同端口,且各自所读结果都已完成。

周期证书还要展开成有限访存轨迹

按两拍分组,0–1、2–3、4–5是逐步填充;6–7这一组包含 A₃、B₂、C₁、D₀,是完整重复图案;8–9、10–11、12–13逐步排空,最后存储于14完成。这里没有把填充和排空当成免费。若逐轮串行用单轮七拍表,四轮需28拍;周期表需14拍。若仅 N=1,周期表反而需8拍,单轮表只需7拍。

真递推限制与下界不充分 ​

把 C 延迟改成3,即使 ALU 仍每拍可启动一次,C 自环也要求 II≥3。II=2 被递推证书直接否决。II=3 时偏移 (0,2,5,8) 合法:Cᵢ 恰好完成时 Cᵢ₊₁ 才启动,Dᵢ 也能读取 cᵢ。容量与递推是两份不同证据。

下界满足也可能无解。另取两个操作 P、Q,均延迟2、均占容量1的同一启动口,边 P→Q 距离0、Q→P 距离2。资源下界2,环下界也为 ceil(4/2)=2。若 II=2,两条依赖迫使 s(Q)−s(P)≥2 且 s(Q)−s(P)≤2,所以差值只能为2;两操作必占同一模槽,违反容量。这个等式冲突才是 II=2 不可行的证明,不是“搜索跑了很久”。II=3 时 P=0、Q=2 则可行。

别名会改变正确答案 ​

现在故意破坏主例前提:让 X[i] 指向共享数组第 i 格,Y[i] 指向同一数组第 i+1 格。初始共享数组为 [1,2,3,4,0]。源循环第0轮写出2,下一轮读到2;第1轮写出6,第2轮随后读到6;于是源输出为 [2,6,18,54]。

错误沿用无别名的 II=2 表会提前加载原来的3和4,仍算出 [2,6,12,20]。端口和原来的四条边都通过,并没有挽救语义。正确依赖图还需要 D→A、距离1,保证上一轮存储完成后下一轮加载才启动。A→B→C→D→A 的延迟和为7、距离和为1,所以 II≥7。依赖分析给出的原距离和存储对象分离证明,是这个优化不可跳过的输入。

推论与应用

从两条证书到每个有限实例 ​

对边 (u,v,d) 和实际消费者轮号 j≥d,把周期不等式加上 (j−d)II,得到 s(v)+jII≥s(u)+(j−d)II+L(u)。所以每一条真正存在的生产者—消费者实例边都满足完成先于启动。j<d 时没有负轮号指令,必须使用合同中给定的初始值;主例只需要 c₋₁=a。不能凭空执行一次“第−1轮”加载或存储。

固定时刻 t 和资源 r。能在此时启动的某种操作 v 至多有一个轮号 i,因为 II>0;它一定满足 s(v)≡t mod II。模槽中最多 cᵣ 种操作,所以该时刻最多 cᵣ 次启动。删除超过 N 的轮号只会减少启动数,因而有限填充和排空同样不超容量。这也说明为什么图案的模检查无需枚举无穷多轮。

对前缀和,X 不变使每个 Aᵢ 采到源循环的 X[i]。按轮号归纳,Bᵢ=kX[i],Cᵢ 在 Bᵢ 和 Cᵢ₋₁ 都交付后启动,故得到 a+∑j=0ikX[j]。Dᵢ 读取这个结果,且只写自己独立的 Y[i]。于是所有输出与源循环相同;访存顺序允许不同,只因已经逐项检查它不会改变任一加载值或覆盖别人的输出。若别名条件失效,这个证明的第一步就失效。

有限展开器为每个 v 与 0≤i<N 建立一个具名实例,逐一生成启动与完成事件。它检查全部实例边和各绝对时刻的启动容量,再实际计算数值并执行每次存储。N>0 且循环体非空时,最后完成时刻为

TN=(N−1)II+maxv(s(v)+L(v));

N=0 或循环体为空时没有任何操作,完成时刻为0,不能把上式机械代成负的启动轮数。这个交付是有限事件程序及其轨迹,不是已经解决分支控制和寄存器轮换的目标汇编循环。

搜索成本与交付边界 ​

一张给定周期表的验证需 O(n+e+r) 时间和空间,模槽用稀疏字典,无需申请长度 II 的数组。下界参考器显式枚举全部简单环,并保存每条证据;环和搜索到的简单路径前缀数量可能指数增长,空间除图本身外,还包含保存环路径的总长度;当前深度优先栈逐层复制路径与已访问集合,另有最坏 O(n²) 的临时空间。这部分发生在试放预算之前,不能把 budget 宣传为整个调用的时间上限。教学脚本只针对小图。

固定 H 后至多有 (H+1)ⁿ 个完整候选,回溯还访问部分候选。若实际试放次数为 B,每次扫描已分配端点的全部边需 O(e+1),故试放阶段为 O(B(e+1));另加输入处理、下界计算及最终验证。活动赋值和槽计数为 O(n),递归深度为 n。Python 递归与内存有限,过大的输入还可能超出实现能力,不能把数学上的有限搜索当成无限资源保证。

展开器先验证原图与周期表,再遍历 N 轮,因此图与证书工作统一为 O((N+1)(n+e+1)+r),峰值空间为 O((N+1)(n+e)+r+1)。这个写法也计入零轮时的原图验证,以及空体时仍遍历 N 轮的参考实现成本。参考数值执行器为清晰起见排序事件时刻,并保存轨迹,另有 O(Nn log(Nn+1)) 的排序时间,轨迹空间已包含在上述界内。以上均不把大整数算术与 JSON 排序算成常数免费工作,也不等同于被调度机器的 T_N。

可执行终点要求分别交付四种搜索状态、真正的边与模槽证书、零轮和一轮边界、完整的四轮访存轨迹,以及别名失效后的错误输出和新增递推。找到较小 II 的时间表是优化结果;证明它在给定语义下可执行,才是交付完成。

参考资料
  • B. Ramakrishna Rau,Iterative Modulo Scheduling: An Algorithm for Software Pipelining Loops,HPL-94-115,1995 扩展报告,§2.3、§2.8、§3.1–3.2、§4:周期重叠、模资源约束、资源/递推下界与实际搜索策略。这里引用的是原报告的公开镜像;本页使用更窄的单类别全流水模型,自行给出有限展开、两操作冲突和别名数值检验
  • 原会议版本的出版记录,MICRO 1994。报告中的真实调度器及机器代码生成比本页参考搜索更完整;本页不将有界穷举等同于原算法
关系图谱6 个相邻概念 · 1 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组