Skip to content

2. 循环与迭代空间优化 (Loop & Iteration Space Optimization)

在确定了算子间的依赖拓扑 (第 1 章) 之后,编译器的优化视角从宏观的数据流图深入到计算内核 (Kernel) 内部的执行逻辑。

本章关注 迭代空间(Iteration Space) 的变换与重组。其核心目标是通过调整计算时序 (Temporal Order),来提升时间局部性 (Temporal Locality),减少循环控制开销 (Loop Overhead),并最大化指令级并行度 (ILP)。编译器通过分析循环嵌套结构和跨迭代的数据依赖,决定如何合并循环范围、在寄存器中复用跨迭代状态,以及如何在指令流水线上掩盖计算延迟。

循环优化是编译器领域的经典研究主题,自1970年代以来的奠基性工作(如Lamport[24]、Wolf & Lam[25]、Allen & Kennedy[23])奠定了现代循环变换的理论基础。多面体编译(Polyhedral Compilation)[1,2,3,4]将这一领域推向了新的高度,实现了自动化的循环优化。

本章所有技术,本质上都是在对 Iteration Space 做代数变换:

  • 合并 (Fusion)
  • 切分 (Tiling)
  • 重排 (Reordering)
  • 提升维度 (Loop Expansion)
  • 消除维度 (Reduction)

2.1 循环融合 (Loop Fusion)

2.1.1 Loop Fusion & Tiling

背景

在底层 IR (如 Affine 或 LLVM IR) 中,循环融合是指将两个具有相同或兼容迭代空间的相邻循环合并为一个循环体的代码变换技术。这一技术是多面体编译的核心优化之一[1,2,4],在PLUTO[1]、TVM[19]等编译器中得到广泛应用。

与图层面的算子融合不同,循环融合更关注指令执行流的优化。其核心收益不仅仅是减少全局内存访问,还包括:

  1. 提升时间局部性 (Temporal Locality):将数据的 "定义""使用" 拉近,使其在寄存器文件L1/L2 Cache 中保持活跃 (Hot),避免被驱逐。Wolf & Lam[25]的研究表明,正确的融合策略可将Cache命中率提升30-50%。

  2. 减少循环控制开销:减少了循环计数器的增量指令、条件分支跳转指令 (Branch) 以及未命中的分支预测惩罚。

  3. 隐式同步消除:在并行编程 (如 OpenMP 或 CUDA) 中,两个独立的循环之间通常隐含一个同步信号 (Barrier)。融合后,这个屏障被消除,减少了线程空转等待[1]。

技术原理

Loop FusionTiling (循环分块) 通常是协同工作的 (统称为 Tile-and-Fuse),其核心原理涉及以下三个层面[1,2,4,25]:

  1. 最大化时空局部性 (Maximizing Locality)

    • Temporal Locality:Fusion 将"生产者"和"消费者"的距离拉近。在生产者计算出 A[i] 后,消费者立刻使用它。如果这个时间间隔足够短,A[i] 仍驻留在 寄存器L1 Cache 中,避免了写入 DRAM 再读回的开销。
    • Spatial Locality:Tiling 将巨大的迭代空间切割成适应 Cache Line 大小的"小块"。这确保了在处理当前块时,相关的数据能装入 Cache 且不发生抖动 (Thrashing)[25]。
  2. 变换机制 (Transformation Mechanics): 编译器通常遵循 "Strip-Mine, Interchange, and Fuse" 的策略[1,23,25]:

    • Strip-Mining:将一个大循环 i 拆分为外层循环 i_outer 和内层循环 i_inner
    • Loop Interchange:调整 outerinner 循环的顺序,将多个算子的 outer 循环对齐[23]。
    • Fusion:当两个算子的外层循环 (Tile Loop) 一致时,合并它们的循环体,使得在一个 Tile 内完成一系列操作。
  3. 合法性检查 (Legality Check): 并非所有循环都能融合。编译器 (如基于多面体模型的 Polyhedral Compiler) 必须构建依赖图 (Dependency Graph),检查是否存在[1,25]:

    • RAW (Read-After-Write) 依赖:融合不能破坏数据的生产-消费顺序。
    • 负距离依赖 (Negative Distance):如果消费者在生产者之前执行 (由于错误的 Tiling 方向),会导致计算错误。

PLUTO[1]通过自动依赖分析和仿射变换,能够安全地应用这些优化。

C++ 示例

cpp
// 融合前
for (i = 0; i < N; i++)
  for (j = 0; j < M; j++)
    A[i][j] = B[i][j] + C[i][j]; // 写入内存

for (i = 0; i < N; i++)
  for (j = 0; j < M; j++)
    D[i][j] = A[i][j] * 2;       // 读取内存

// 融合后
for (i = 0; i < N; i++)
  for (j = 0; j < M; j++) {
    float temp_a = B[i][j] + C[i][j]; // 临时变量,驻留寄存器
    // A[i][j] = temp_a;              // 可选:若 A 不再被使用,则消除此写操作 (Store Elimination)
    D[i][j] = temp_a * 2;
  }

MLIR Affine 实现

MLIR的Affine方言[22]提供了丰富的循环变换原语,包括affine.loop_fusionaffine.tile等,这些变换基于多面体编译理论[1,2,4]。

cpp
// 融合前
affine.for %i = 0 to 100 {
  affine.for %j = 0 to 100 {
    %a = affine.load %A[%i, %j] : memref<100x100xf32>
    %b = affine.load %B[%i, %j] : memref<100x100xf32>
    %sum = arith.addf %a, %b : f32
    affine.store %sum, %C[%i, %j] : memref<100x100xf32>
  }
}

affine.for %i = 0 to 100 {
  affine.for %j = 0 to 100 {
    %c = affine.load %C[%i, %j] : memref<100x100xf32>
    %d = arith.mulf %c, %c : f32
    affine.store %d, %D[%i, %j] : memref<100x100xf32>
  }
}

// 融合后(-affine-loop-fusion)
affine.for %i = 0 to 100 {
  affine.for %j = 0 to 100 {
    %a = affine.load %A[%i, %j] : memref<100x100xf32>
    %b = affine.load %B[%i, %j] : memref<100x100xf32>
    %sum = arith.addf %a, %b : f32
    // %C 的 store 操作被消除,物化 %sum 直接用于下一步计算
    %d = arith.mulf %sum, %sum : f32
    affine.store %d, %D[%i, %j] : memref<100x100xf32>
  }
}

2.2 跨迭代状态优化 (Cross-iteration State Optimization)

此类优化关注跨越时间步 (Time Step) 或循环迭代 (Iteration) 的数据状态管理。

2.2.1 Loop-carried Scalar Replacement / Accumulator Fusion (循环携带标量替换 / 累加融合)

背景

某些循环带有循环携带依赖 (Loop-carried Dependency),如求和累加器 (Summation) 或最大值扫描 (Max Scan)。融合时必须显式维护这些状态,防止中间结果溢出到内存。

标量替换(Scalar Replacement)是编译器优化的经典技术,在Array SSA形式[8]和现代编译器优化理论[12]中得到深入研究。

技术原理

标量替换 (Scalar Replacement of Aggregates, SROA)寄存器提升 (Register Promotion) 是实现累加器融合的底层机制[8,12]:

  1. 消除读-改-写循环 (Read-Modify-Write Elimination)

    • 未优化:在循环的每一次迭代中,执行 Load (Mem) -> Add -> Store (Mem)。这会导致 次内存访问。
    • 优化后:编译器识别出该变量具有 Loop-carried Dependency,将其"提升"为寄存器变量。循环变为 Reg = Reg + Val,仅在循环结束后执行一次 Store (Mem)。内存访问降为 2 次 (读初值 + 写终值)。
  2. SSA 形式与 Phi 节点 (SSA & Phi-Nodes): 在现代编译器 IR (如 MLIR/LLVM) 中,寄存器状态通过 SSA (静态单赋值) 形式建模[8,12]:

    • 循环的 入口参数 (Block Arguments / iter_args) 充当了 Phi 节点,负责合并来自"循环入口"的初始值和"循环回边 (Back-edge)"的更新值。
    • 后端寄存器分配器 (Register Allocator) 会直接将这些 SSA 值映射到物理累加寄存器 (如 GPU 的 RAccum 寄存器)。

收益

  1. 消除内存流量:对于长度为 的循环,消除 次内存读写。
  2. 打破延迟链:通过寄存器直接转发数据,缩短指令间的依赖延迟 (Latency Hiding)。

MLIR 实现方案

MLIR 的 scf.foraffine.for 引入了 iter_args 机制,这是显式表达寄存器驻留状态的各种 IR 中的最佳实践。它将可变变量 (Mutable Variables) 转化为 SSA 值 (Immutable Values) 的传递。

MLIR 示例

cpp
// 场景:计算点积 Dot(A, B)
// 优化前(概念):频繁读写内存
%sum_ptr = memref.alloc() : memref<f32>
scf.for %i = 0 to 1024 {
  %old_sum = memref.load %sum_ptr[] : memref<f32>
  %prod = ...
  %new_sum = arith.addf %old_sum, %prod
  memref.store %new_sum, %sum_ptr[] : memref<f32> // 瓶颈:写回内存
}

// 优化后(MLIR iter_args):全寄存器操作
// %sum_iter 在编译后直接映射为物理寄存器
%final_sum = scf.for %i = 0 to 1024 
  iter_args(%sum_iter = %initial_sum) -> (f32) {
  
  %a = load %A[%i]
  %b = load %B[%i]
  %prod = arith.mulf %a, %b : f32
  
  // 状态更新仅发生在 SSA 值之间
  %sum_next = arith.addf %sum_iter, %prod : f32
  
  // 将新状态传递给下一次迭代
  scf.yield %sum_next : f32
}

2.2.2 Temporal Fusion (时间融合)

背景

循环神经网络 (RNN/LSTM/GRU) 包含一个时间维度的循环:

  • 未融合:通常实现为"循环展开"或多次 Kernel 启动。每一步计算完 后,将其写入全局内存,下一步再读出。对于长序列 (如 ),这会产生巨大的内存带宽开销。
  • 时间融合:将整个时间循环编译为一个 Kernel。状态变量 通过寄存器 (Register)共享内存在迭代间直接传递,只在最后一步输出结果或在必要时保存历史用于反向传播。

Cortex[20]、Echo[18]等编译器针对RNN/LSTM的时间融合优化进行了深入研究,证明可减少60-80%的内存访问。

技术原理

Loop-Carried Dependency Optimization: 利用 MLIR 的 scf.for(结构化控制流循环) 中的 iter_args 机制,显式地表达跨迭代的数据依赖。后端编译器 (如 LLVM 或 NVVM) 会将这些 iter_args 提升为物理寄存器 (Register Promotion),从而消除循环间的 Load/Store。

MLIR 实现:LSTM State Persistence

cpp
// 融合后的 LSTM 时间循环
// 输入:整个输入序列 %seq [SeqLen, Batch, InputDim]
//       初始状态 %h_init, %c_init
// 输出:最终状态 %h_final, %c_final
func.func @lstm_temporal_fused(%seq: tensor<?x?x?xf32>, 
                               %h_init: tensor<?x?xf32>, 
                               %c_init: tensor<?x?xf32>,
                               %weights: tensor<...>) 
                               -> (tensor<?x?xf32>, tensor<?x?xf32>) {
  
  // 获取序列长度 T
  %c0 = arith.constant 0 : index
  %c1 = arith.constant 1 : index
  %T = tensor.dim %seq, %c0 : tensor<?x?x?xf32>

  // 核心融合:scf.for 携带 iter_args
  // %iter_h, %iter_c 在循环迭代中驻留在寄存器/L1中,不写回 HBM
  %h_final, %c_final = scf.for %t = %c0 to %T step %c1 
      iter_args(%curr_h = %h_init, %curr_c = %c_init) 
      -> (tensor<?x?xf32>, tensor<?x?xf32>) {
    
    // 1. 读取当前时刻输入 x[t]
    %xt = tensor.extract_slice %seq[%t, 0, 0] [...] : tensor<?x?x?xf32> to tensor<?x?xf32>

    // 2. [Cell Fusion] 融合 LSTM Cell 内部的矩阵乘与激活函数
    // MatMul: gates = x[t] @ W + curr_h @ R + bias
    // 这一步通常也会应用 Element-wise Chain Fusion
    %gates_raw = linalg.matmul ins(%xt, %curr_h, %weights : ...) ...

    // 3. 计算 Gate Activation (Sigmoid/Tanh) 和状态更新
    // next_c = f * curr_c + i * g
    // next_h = o * tanh(next_c)
    %next_c = linalg.generic ... ins(%gates_raw, %curr_c) ... 
    %next_h = linalg.generic ... ins(%gates_raw, %next_c) ...

    // 4. 将更新后的状态 Yield 给下一次迭代
    // 编译器后端将其识别为 Loop-carried variable,分配寄存器
    scf.yield %next_h, %next_c : tensor<?x?xf32>, tensor<?x?xf32>
  }

  return %h_final, %c_final
}

代码解析

  1. scf.for ... iter_args (%curr_h, %curr_c):这是时间融合的关键。它告诉编译器 %curr_h%curr_c 是随着循环演进的变量。在生成机器码时,这些变量会被映射到寄存器 (如 GPU 的 RF 或 CPU 的 Vector Regs)。
  2. linalg.generic (Cell内部):LSTM Cell 内部的复杂门控逻辑 (Sigmoid, Tanh, Element-wise Mul/Add) 被融合为大的计算块,避免中间结果物化。
  3. 零中间内存:除了输入的 %seq 和权重的 %weights,中间产生的 不需要写回主存 (除非是为了训练时的反向传播 Checkpointing)。

2.2.3 Gradient Accumulation & Implicit Transpose (梯度累积与隐式转置)

背景

  1. 梯度累积 (Gradient Accumulation):为了在有限显存下模拟大 Batch Size,训练通常将一个大 Batch 切分为多个 Micro-Batch 串行执行,梯度需要跨迭代累加。
  2. 反向转置 (Backward Transpose):在计算 Linear 或 Conv 层对输入的梯度时 (),数学上要求权重的转置。显式执行 Transpose (W) 会产生巨大的内存拷贝开销。

技术原理

  1. Accumulator Persistence (累加器驻留): 编译器将梯度 Buffer 标记为 Output Stationary。在循环处理 Micro-Batch 时,将累加结果保留在 寄存器L2 Cache 中,直到所有 Micro-Batch 处理完毕才刷回 HBM。
  2. Implicit Transpose (隐式转置): 编译器不生成物理转置,而是通过修改 MatMul 的 Indexing Map (索引映射),指示硬件以"转置的步长"读取原始权重矩阵。

MLIR 实现

cpp
// 1. Gradient Accumulation (跨迭代累加)
// scf.for 循环携带 iter_args (accumulated_grad)
%total_grad = scf.for %i = 0 to %num_micro_batches 
    iter_args(%acc_grad = %zero_grad) -> (tensor<?x?xf32>) {
  
  // 计算当前 Micro-Batch 的梯度
  %mb_grad = call @backward_step(%i)
  
  // 2. Implicit Transpose Fusion (在 MatMul 中融合转置)
  // 计算 dX = dY * W^T
  // 注意 indexing_maps 中 W 的访问顺序是 (d1, d0) 而非 (d0, d1)
  %dx = linalg.generic {
     indexing_maps = [
       affine_map<(d0, d1, d2) -> (d0, d2)>, // dY: [M, K]
       affine_map<(d0, d1, d2) -> (d1, d2)>, // W:  [N, K] (物理上是 [N, K],逻辑上被视为转置)
       affine_map<(d0, d1, d2) -> (d0, d1)>  // dX: [M, N]
     ],
     iterator_types = ["parallel", "parallel", "reduction"]
  } ins(%dy, %w : ...) outs(...) {
     ^bb0(%a: f32, %b: f32, %c: f32):
       %prod = arith.mulf %a, %b : f32
       %sum = arith.addf %prod, %c : f32
       linalg.yield %sum : f32
  }

  // 累加到总梯度
  %new_acc = arith.addf %acc_grad, %dx : tensor<?x?xf32>
  scf.yield %new_acc : tensor<?x?xf32>
}

2.3 循环展开与流水线 (Loop Unrolling & Pipelining)

此技术通过重组循环内的指令调度,最大化硬件单元的利用率,主要解决指令流水线气泡和内存延迟问题。软件流水线(Software Pipelining)技术自1990年代Rau[13]的开创性工作以来,已成为高性能编译器的标准优化。

2.3.1 Loop Unrolling (循环展开)

背景

现代 CPU/GPU 拥有超标量架构 (Superscalar),每个时钟周期可发射多条指令。紧凑的循环 (Tight Loop) 由于频繁的分支跳转检测 (Branch Compare & Jump),会打断指令流水线。

循环展开通过复制循环体代码,减少跳转次数,并暴露更多的独立指令供硬件调度器进行 指令级并行 (ILP) 优化。这一技术与向量化(Vectorization)[23]密切相关。

技术原理 (通用架构)

循环展开的性能收益主要来自三个微观层面:

  1. 摊薄控制流开销 (Amortizing Control Overhead): 原始循环每执行一次计算,都要执行一次"比较"和"跳转"。展开 次后,这 次计算共享同一个跳转指令。控制指令在总指令数中的占 比大幅下降,显著减轻了 CPU/GPU 分支预测器 (Branch Predictor) 的压力。

  2. 暴露指令级并行 (Exposing ILP): 现代处理器 (超标量或 VLIW 架构) 拥有多个执行端口 (如多个 ALU 和 Load/Store 单元)。展开后,来自不同迭代的独立指令 (如 ii+1) 被同时暴露给硬件调度器,可以同时发射到不同的执行单元,填满硬件流水线的空槽 (Slots)。

  3. 扩大调度窗口 (Expanding Scheduling Window): 展开增加了 基本块 (Basic Block) 的大小。这给编译器提供了更大的指令重排空间,使其能够将"高延迟指令 (如 Load)"与其"依赖指令 (Use)"拉开距离,从而利用乱序执行 (Out-of-Order) 掩盖内存延迟。

硬件视角:Ascend NPU 的特殊优化

Ascend NPU (达芬奇架构) 上,循环展开的意义与通用 CPU/GPU 有所不同,它主要为了适配 Vector Repeat 机制和掩盖异构核心通信延迟

  1. Repeat 指令映射 (Hardware Repeater Mapping): Ascend ISA 支持强大的 Repeat 机制。一条 Vector 指令 (如 vadd) 可以通过设置 repeat_timesstride,一次性处理多达 255 个连续数据块。

    • 编译器策略:编译器先在 IR 层面展开循环,识别出连续的计算模式,然后在后端代码生成时将其 再折叠 (Re-roll) 为单条带 Repeat 的指令。这极大地减少了取指译码 (Fetch/Decode) 带宽。
  2. 掩盖 Scalar-Vector 依赖 (Hiding Scalar-Vector Latency): AICORE 是异构多核架构:Scalar Unit 负责控制流和地址计算,Vector Core 负责密集运算。

    • 优化逻辑:通过 Loop Unrolling,编译器可以生成一个"大工作量"的 Vector 指令块 (或 Repeat 指令)。当 Vector Core 忙于处理这就绪的数据块时,Scalar Core 可以并行地计算下一批数据的地址。这种解耦 (Decoupling) 确保了计算流水线永不干涸。

收益

  1. 减少分支开销 次迭代变成 次跳转。
  2. 向量化机会:展开后的连续访存指令更容易被合并为向量加载 (Vector Load)。

MLIR 实现

affinescf 方言中,可以通过属性标记或变换 Pass 显式控制展开因子。

cpp
// 原始循环
affine.for %i = 0 to 1024 {
  %x = affine.load %A[%i]
  %y = affine.load %B[%i]
  %z = arith.addf %x, %y : f32
  affine.store %z, %C[%i]
}

// 优化后:Unroll Factor = 4
// 1. 控制流指令减少 4 倍
// 2. 暴露出 4 个独立的 Load/Add/Store,利于 SIMD/SIMT 打包
affine.for %i = 0 to 1024 step 4 {
  // Iteration i
  %x0 = affine.load %A[%i]
  %z0 = arith.addf ... 
  
  // Iteration i+1
  %x1 = affine.load %A[%i + 1]
  %z1 = arith.addf ...
  
  // ... i+2, i+3 ...
  
  // Ascend 后端识别:
  // 发现 %x0, %x1... 地址连续,%z0, %z1... 计算逻辑一致
  // -> 可能会合并生成一条 Vector Load 和一条 Vector Add (with Repeat)
}

2.3.2 Software Pipelining (软件流水线)

背景

在深度学习算子 (如 GEMM, Attention) 中,从全局内存 (HBM) 加载数据到片上缓存 (SRAM/Register) 的延迟极高 (数百个时钟周期)。

如果采用 Load Compute 的串行模式,计算单元在等待数据时会空转。

软件流水线 (配合双缓冲/多缓冲 Double Buffering) 将不同迭代的阶段重叠执行:在计算当前块 (Tile ) 的同时,预取下一块 (Tile ) 的数据,从而实现Latency Hiding (延迟隐藏)

这一技术源于Rau[13]的迭代模调度(Iterative Modulo Scheduling)理论,在LLVM[14]、ALCOP[15]等现代编译器中得到实现和应用。

技术原理

流水线变换将循环重构为三个部分[13,14,15]:

  1. Prologue (序言):预取第 0 次迭代的数据。
  2. Steady State (稳态/核):同时执行第 次计算和第 次加载。
  3. Epilogue (尾声):完成最后一次迭代的计算。
Timeline:
Iter 0: [Load 0]
Iter 1:          [Comp 0] [Load 1]  <-- 并行执行 (Overlap)
Iter 2:                   [Comp 1] [Load 2]

MLIR 实现 (Async Pipelining)

MLIR 的 scf.for 配合 iter_args 和异步指令 (如 nvgpu.device_async_copy) 可以完美表达这种模式。ALCOP[15]针对GPU深度学习工作负载,实现了自动化的Load-Compute流水线优化。

cpp
// 软件流水线化后的循环结构
// 初始阶段:预加载第 0 个 Tile (Prologue)
%token0 = gpu.async_copy %Global[%c0] to %Shared[%c0] ...

// 循环携带 token 状态 (Stage i 的加载句柄传递给 Stage i+1)
scf.for %i = 0 to %N step %TileSize 
  iter_args(%token_prev = %token0) {
  
  // 1. 发起第 $i+1$ 个 Tile 的异步加载 (Prefetch)
  %next_idx = arith.addi %i, %TileSize
  %token_next = gpu.async_copy %Global[%next_idx] to %Shared[%buffer_next] ...
  
  // 2. 等待第 $i$ 个 Tile 加载完成 (Wait)
  gpu.wait %token_prev
  
  // 3. 计算第 $i$ 个 Tile (Compute)
  // 此时计算与步骤 1 的加载是并行的
  linalg.matmul ins(%Shared[%buffer_curr] ...)
  
  // 4. 传递下一轮的 token
  scf.yield %token_next
}

// 尾声:计算最后一个 Tile (Epilogue)
gpu.wait %token_last
linalg.matmul ...

参考文献 (References)

多面体编译与循环融合 (Polyhedral Compilation & Loop Fusion)

[1] Uday Bondhugula, Albert Hartono, J. Ramanujam, et al. "A Practical Automatic Polyhedral Parallelizer and Locality Optimizer (PLUTO)." ACM PLDI, 2008. [Link]

[2] Uday Bondhugula. "Effective Automatic Parallelization and Locality Optimization Using The Polyhedral Model." PhD Thesis, Indian Institute of Science, 2008. [Link]

[3] Uday Bondhugula, Aravind Acharya, Albert Cohen. "The Pluto+ Algorithm: A Practical Approach for Parallelization and Locality Optimization." ACM TOPLAS, 2016. [Link]

[4] Tobias Grosser, Albert Cohen, J. Ramanujam, et al. "A Survey of General-Purpose Polyhedral Compilers." ACM Computing Surveys, 2022. [Link]

[5] (PLDI 2020). "Effective Loop Fusion in Polyhedral Compilation Using Fusion Conflict Graphs." ACM TACO, 2020. [Link]

[6] (PLDI 2024). "Modeling the Interplay between Loop Tiling and Fusion." ACM, 2024. [Link]

[7] G. Gao, et al. "Collective Loop Fusion for Array Contraction." LCPC 1992. [Classic]

[8] P. Marchal, et al. "Optimizing the Memory Bandwidth with Loop Fusion." CODES+ISSS, 2004. [Link]

循环携带依赖与状态优化 (Loop-carried Dependence & State Optimization)

[9] (Rice University). "Inter-iteration Scalar Replacement Using Array SSA Form." Rice University. [Link]

[10] (CMU). "Compiler Optimization of Scalar Value Communication." ASPLOS, 2002. [Link]

[11] J.R. Allen, K. Kennedy. "Automatic Loop Interchange." SIGPLAN, 1984. [Link]

[12] K. Kennedy. "Automatic Translation of Fortran Programs to Vector Form." ACM TOPLAS, 1987. [Link]

[13] K. Cooper. "Compiler-Based Code-Improvement Techniques." [Survey]

循环展开与流水线 (Loop Unrolling & Pipelining)

[14] B.R. Rau. "Iterative Modulo Scheduling: An Algorithm For Software Pipelining." Micro, 1994. [Link]

[15] T.M. Lattner. "Swing Modulo Scheduling Implementation." Masters Thesis, 2005. [LLVM SMS]

[16] Z. Zhang, et al. "SDC-Based Modulo Scheduling for Pipeline Synthesis." ICCAD, 2013. [Link]

[17] B.L. Huber. "Software Pipelining in a C-Compiler." TU Wien, 2008. [Link]

[18] (MLSys 2023). "ALCOP: Automatic Load-COmpute Pipelining in Deep Learning." MLSys, 2023. [Link]

经典基础工作 (Classic Foundations)

[19] Leslie Lamport. "The Parallel Execution of DO Loops." Communications of the ACM, 1974. [Link]

[20] Michael Wolf, Monica S. Lam. "A Loop Transformation Theory." ACM TOPLAS, 1991. [Link]

[21] Michael Wolfe. "A Data Locality Optimizing Algorithm." ACM PLDI, 1989. [Link]

[22] B.R. Rau, et al. "Automatic Pipelining of Loops." 1992. [Classic]

深度学习特定优化 (Deep Learning Specific Optimizations)

[23] (arXiv 2018). "Echo: Compiler-based GPU Memory Footprint Reduction." arXiv, 2018. [Link]

[24] (MLSys 2021). "Cortex: A Compiler for Recursive Deep Learning Models." MLSys, 2021. [Link]

[25] Tianqi Chen, et al. "TVM: An Automated End-to-End Optimizing Compiler for Deep Learning." USENIX OSDI, 2018. [Link]

[26] (Semantic Scholar). "EcoRNN: Fused LSTM RNN Implementation." [Link]

[27] (ScienceDirect 2024). "RNN-LSTM: From Applications to Modeling Techniques." 2024. [Link]

MLIR与现代编译器框架 (MLIR & Modern Compiler Frameworks)

[28] (CGO 2025). "The MLIR Transform Dialect." CGO, 2025. [Latest]

[29] (Oxford). "MLIR: An Optimizing Compiler Framework." Oxford Technical Report. [Link]

[30] (Polygeist). "Polygeist: Affine C in MLIR." [Link]

Released under the CC BY-NC-ND 4.0 License.