调度pass的定制

概述

作为调度pass实现类,MachineScheduler 和 PostMachineScheduler 的优点之一是允许开发者在LLVM 已有调度算法和策略基础上做不同的定制化

PostMachineScheduler 类的定制相对简单,主要是MachineSchedStrategy 接口的定制。 MachineScheduler 类的定制化更为复杂,主要包括三个方面: 调度策略定制、MachineSchedStrategy 接口定制和ScheduleDAGMILive 类定制

调度策略定制

MachineScheduler 类仅负责选择要调度的区域。如果目标后端不需要要通过定制MachineSchedStrategy 接口对区域调度做过多定制,而只是希望调整调度策略类GenericScheduler 的某些策略,如定制 MachineScheduler 调度方向、寄存器压力跟踪策略等,可以通过指定调度器的某些高层配置,实现定制调度策略

这些高层配置一般在 overrideSchedPolicy() 函数中指定,以此在子目标层级设定指定调度的配置选项。例如,AMDGPU后端GCN子目标的重写 overrideSchedPolicy() 函数实现代码如下

void GCNSubtarget::overrideSchedPolicy(MachineSchedPolicy &Policy,
                                      unsigned NumRegionInstrs) const {
  // Track register pressure so the scheduler can try to decrease
  // pressure once register usage is above the threshold defined by
  // SIRegisterInfo::getRegPressureSetLimit()
  Policy.ShouldTrackPressure = true;

  // Enabling both top down and bottom up scheduling seems to give us less
  // register spills than just using one of these approaches on its own.
  Policy.OnlyTopDown = false;
  Policy.OnlyBottomUp = false;

  // Enabling ShouldTrackLaneMasks crashes the SI Machine Scheduler.
  if (!enableSIScheduler())
    Policy.ShouldTrackLaneMasks = true;
}

其中,结构体 MachineSchedPolicy 定义了 MachineSchedStrategy 接口中没有提供的调度策略

  • 例如,MachineSchedPolicy 中的 ShouldTrackPressure 字段表示调度器开启寄存器压力跟踪
  • 一旦寄存器使用量超过SIRegisterInfo::getRegPressureSetLimit() 定义的阀值,调度器可以尝试降低寄存器压力
  • 寄存器分配的指令调度不需要跟踪寄存器压力

MachineSchedPolicy 中 的 OnlyTopDonw 和 OnlyBottomUp 字段分别表示调度强制自顶向下或自底向上调度指令。如果二者都为false,则表示双向调度

GCN 子目标使用了双向调度策略,因为开发者认为这样可以减少寄存器溢出。用户可以通过llc命令行选项-misched-topdown/bottomup 改变默认策略。 MachineSchedPolicy 中的字段ShouldTrackLaneMasks 表示调度器是否跟踪LaneMask

LaneMask 是寄存器的位掩码,通常用于显示组成寄存器的子寄存器的活跃性。跟踪LaneMask有助于对子寄存器的写入操作进行重新排序

MachineSchedStrategy 接口定制

LLVM 中的大多数后端不需要重写DAG构建器和列表调度,但如果某些后端的子目标需要定制调度启发式策略,则可以通过定制 MachineSchedStrategy 接口,并在其中实现自定义调度算法

MachineSchedStrategy 接口需要实现用于跟踪、保存就绪节点的优先级队列,以及操作队列的函数,包括队列中添加节点、从队列中调度节点的操作等

  • 例如:AMDGPU 后端的R600子目标定义了 MachineSchedStrategy 接口类R600SchedStrategy,其实现代码如下:
class R600SchedStrategy final : public MachineSchedStrategy {
  void initialize(ScheduleDAGMI *dag) override;
  SUnit *pickNode(bool &IsTopNode) override;
  void schedNode(SUnit *SU, bool IsTopNode) override;
  void releaseTopNode(SUnit *SU) override;
  void releaseBottomNode(SUnit *SU) override;
 }

MachineSchedStrategy 类中定义了保存就绪节点的队列,以及操作队列的releaseTopNode(),releaseBottomNode() 等函数

上述代码中的pickNode() 函数是指令调度框架的入口函数,其中根据 “AMD Accelerated Paralled Processing OpenCL Programming Guide” 文档的要求实现了R600 的调度策略

  • 如果R600SchedStrategy 类的pickNode() 函数没有选中符合条件的节点,仍可调用GenericScheduler 类的 pickNode() 函数完成调度

上述代码中的 schedNode() 函数在节点被调度后被调用,可用于更新调度器的内部状态,如已发射指令计数等

R600 子目标可以在调用 createMachineScheduler() 函数为标准 MachineScheduler 调度 pass 生成 ScheduleDAGInstrs 实例时,在 ScheduleDAGILive 实例中 插入自定义 MachineSchedStrategy 接口 R600SchedStrategy, 以此决定调度节点方式

class R600PassConfig final : public AMDGPUPassConfig {
public:
  R600PassConfig(LLVMTargetMachine &TM, PassManagerBase &PM)
      : AMDGPUPassConfig(TM, PM) {}

  ScheduleDAGInstrs *
  createMachineScheduler(MachineSchedContext *C) const override {
    return createR600MachineScheduler(C);
  }

  bool addPreISel() override;
  bool addInstSelector() override;
  void addPreRegAlloc() override;
  void addPreSched2() override;
  void addPreEmitPass() override;
};

上述通过重写 MachineSchedStrategy 接口的方法实现自定义调度算法的工作量较大。如果希望仅对 GenericScheduler 类中已有的启发式策略做调整,但复用大部分调度框架基础设施,则可以由GenericScheduler 类派生自定义子类,在其中添加定制调度策略,实现某些GenericScheduler 类中未定义的架构行为

  • 例如,AMDGPU 后端的GCN 子目标定义了自己的 MachineSchedStrateg 派生类GCNMaxOccupancySchedStrategy。
  • CGNMAxOccpancySchedStrategy 类与GenericScheduler 不同之处在于,GCNMaxOccupancySchedStrategy 类使用不同的启发式策略确定寄存器超额(excess) 和 临界(critical) 压力,其目标是最大化内核占用率,即最大化每个SIMD的最大wavefront 数

与R600 子目标实现方式类似,GCN子目标的 createMachineScheduler() 函数通过调用 createGCNMaxOccupancyMachineScheduler() 函数,在生成GCNScheduleDAGMILive实例时插入自定义 GCNMaxOccupancySchedStrategy接口

/// Instantiate a ScheduleDAGInstrs that will be owned by the caller.
ScheduleDAGInstrs *MachineScheduler::createMachineScheduler() {
  // Select the scheduler, or set the default.
  MachineSchedRegistry::ScheduleDAGCtor Ctor = MachineSchedOpt;
  if (Ctor != useDefaultMachineSched)
    return Ctor(this);

  // Get the default scheduler set by the target for this function.
  ScheduleDAGInstrs *Scheduler = PassConfig->createMachineScheduler(this);
  if (Scheduler)
    return Scheduler;

  // Default to GenericScheduler.
  return createGenericSchedLive(this);
}

/// Create the standard converging machine scheduler. This will be used as the
/// default scheduler if the target does not set a default.
ScheduleDAGMILive *llvm::createGenericSchedLive(MachineSchedContext *C) {
  ScheduleDAGMILive *DAG =
      new ScheduleDAGMILive(C, std::make_unique<GenericScheduler>(C));
  // Register DAG post-processors.
  //
  // FIXME: extend the mutation API to allow earlier mutations to instantiate
  // data and pass it to later mutations. Have a single mutation that gathers
  // the interesting nodes in one pass.
  DAG->addMutation(createCopyConstrainDAGMutation(DAG->TII, DAG->TRI));
  return DAG;
}

createGenericSchedLive() 函数中调用了函数 addMutation(),其目的是在 DAG 构造器中增加一些后处理步骤,这些后处理步骤以ScheduleDAGMuation 对象的作用是在调度前根据硬件目标相关知识,向数据依赖图中添加TableGen语言不能表达的调度约束,即通过在数据依赖图中增加边调整图中的依赖关系,其代价是降低了调度的灵活性

上述代码中添加了三个ScheduleDAGMuation对象,这三个对象按照其添加的顺序,依次在正常DAG构造后作用于DAG

ScheduleDAGMILive 类定制

通常,定制目标后端的MachineSchedStrategy 接口就应该足以实现新的调度算法。不过,目标后端调度程序可以通过进一步派生自己的ScheduleDAGMILive 子类试想调度器,并重载其schedule() 虚函数,实现任何后端特定的调度处理. 例如,AMDGPU 后端的GCN子目标定义了自己的 ScheduleDAGMILive派生类 GCNScheduleDAGMILive,定义如下:

class GCNScheduleDAGMILive final : public ScheduleDAGMILive {
    void schedule() override;

    void finalizeSchedule() override;
}

finalizeSchedule() 函数允许目标后端在MachineFunction 级别执行最终的调度操作,即遍历所有的区域,通过寄存器压力监视器RPTracker计算区域的寄存器压力,然后调用 schedule()

GCNScheduleDAGMILive::schedule() 函数实现了 ScheduleDAGInstrs 调度指令序列接口,并通过上述GCNMaxOccupancySchedStrategy 调度策略接口,完成GCN子目标特定的调度处理。

ScheduleDAGMILive::schedule() 函数通过调度策略实现类对象 SchedImpl,调用GCNMaxOccpuancySchedStrategy类的 pickNode() 函数选择调度节点

此外,还可以通过GCNMaxOccupancySchedStrategy 调度策略接口判断寄存器压力是否超过限制


  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值