LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_llvm编译器指令-程序员宅基地

技术标签: LLVM  后端  技术文章  

1、参考Instruction scheduling in LLVM - 知乎,在中、后端均存在指令调度

     https://www.youtube.com/watch?v=cWLW0aQwFg0&t=922s

GenericScheduler:: 做寄存器压力感知的指令调度

PostGenericScheduler:: 寄存器分配后的指令调度,基于BB的指令调度

 2、在llvm12->llvm14 中Machine Instruction Scheduler有个patch af342f7240增强了load/store指令的合并,也就是指令调度不仅仅只是改变指令的顺序,该优化类似gcc中的store-merge优化

注意:后端isPairableLdStInst接口可以指示是否允许寄存器对的优化,一般来说这个合并是有利的,也不能排除个别硬件存在bug

3、指令调度*.P指Pend, *.A为ready的指令,调度时从Bot和Top两端选取,最后将在某个中间位置相遇时结束调度;另外BotQ.A中的指令会被优先考虑发射(P 代表Pending, A 代表Available, 参考SchedBoundary::pickOnlyChoice)

4、指令中隐式的定义需要被列在let Defs = []中,参考 D123578 [RISCV] Add sched to pseudo function call instructions

let isCall = 1, Defs = [LR, X0, X1], hasSideEffects = 1, Size = 16,
    isCodeGenOnly = 1 in
def TLSDESC_CALLSEQ
    : Pseudo<(outs), (ins i64imm:$sym),
             [(AArch64tlsdesc_callseq tglobaltlsaddr:$sym)]>,
      Sched<[WriteI, WriteLD, WriteI, WriteBrReg]>;

AArch64::TLSDESC_CALLSEQ lower to

adrp  x0, :tlsdesc:var

ldr   x1, [x0, #:tlsdesc_lo12:var]

add   x0, x0, #:tlsdesc_lo12:var

blr   x1 # bl 指令会将程序的返回地址放到LR寄存器中 

5、 乱序执行OOO也需要进行指令调度优化,不是任意顺序性能都一样的

硬件有一个instruction window, 假如这个window无限大,compiler不管怎么写指令都无所谓。但实际上这个window只有80-100个指令,所以当有很深的parallel loop时,compiler如何写指令就会对性能有影响了

6、实现指令调度,确保stp指令按照升序排列输出(寄存器分配后的指令调度简单从ReadyQueue队列中获取一个指令,然后和当前Cand进行比较,如果需要调整即更新Cand, 因此pickNodeFromQueue函数返回后Cand值即为最优先需要发射的指令)

D125377 [AArch64] Order STP Q's by ascending address (llvm.org)

注意:指令调度的模型SchedModel=mf.getSubtarget()和后端架构配置相关

7、 在-mattr=+slow-paired-128选项说明128位的loadstore不好可能是一个共性问题,commit 7784cacd9,涉及AArch64InstrInfo::isCandidateToMergeOrPair函数

 203 def FeatureSlowPaired128 : SubtargetFeature<"slow-paired-128",
 204     "Paired128IsSlow", "true", "Paired 128 bit loads and stores are slow">;

usage:clang  -Xclang -target-feature -Xclang +slow-paired-128 或者llc -mattr=+slow-paired-128

代码中选项使用方法,参考MF.getSubtarget<AArch64Subtarget>().hardenSlsBlr() (注:需要头文件#include "AArch64Subtarget.h")

8、指令调度模型在 llvm/lib/Target/AArch64/AArch64.td中设置,参考D89972 (之后在D120906中进行了重构),也就是当前指定使用ProcTSV110对应的模型配置

当仅仅使用 -march=armv8.2-a 未指定tsv110时使用默认"generic"对应的CortexA55Model

:def : ProcessorModel<"generic", CortexA55Model, ProcessorFeatures.Generic
+def : ProcessorModel<"tsv110", TSV110Model, [ProcTSV110]>;

9、GenericSchedulerBase::shouldReduceLatency  确认指令的时序是否要减少?(已经大于关键路径)

10、in-order和out-of-order的关键配置(相关处理CodeGen/MachineScheduler.cpp)

Target/RISCV/RISCVSchedSiFive7.td:13:  let MicroOpBufferSize = 0; // Explicitly set to zero since SiFive7 is in-order

11、将循环中的LoopMicroOpBufferSize值调小一点,参考72a799a6

12、Load/Store的时延分析特别处理,参考D8705,load->store的时延只有1个cycle?

13、指令调度中每个指令被匹配为一个SUnit结点,根据top-down topological order计算两个指令的顺序,也就是A<B代表无B->A的边(即A可以在B前发射),参考ScheduleDAGInstrs::initSUnits

SU->Latency = SchedModel.computeInstrLatency(SU->getInstr())

14、通过ARMTargetLowering::getSchedulingPreference设置特殊指令的调度模式(用于寄存器分配前的指令调度)

enum Preference {
  None,        // No preference
  Source,      // Follow source order.
  RegPressure, // Scheduling for lowest register pressure.
  Hybrid,      // Scheduling for both latency and register pressure.
  ILP,         // Scheduling for ILP in low register pressure mode.
  VLIW,        // Scheduling for VLIW targets.
  Fast,        // Fast suboptimal list scheduling
  Linearize    // Linearize DAG, no scheduling
};

15、实现模型的定义,比如将 WriteV进一步区分WriteVd和WriteVq的描述,参考D108766

16、使用llvm-mca -mtriple=aarch64 -mcpu=tsv110 -instruction-tables < test.s可以显示编译器定义的时序信息,方便和文档做比较,参考D128631 (Gcc 侧的定义commit 25095d1ef8)

llvm/utils/update_mca_test_checks.py --llvm-mca-binary=build/bin/llvm-mca xx.s

Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)

[1]    [2]    [3]    [4]    [5]    [6]    Instructions:
 2      5     1.00    *                   ldp    s7, s16, [x24, #-8]
 1      1     0.50                        add    x13, sp, #272

Resources:
[0]   - A57UnitB
[1.0] - A57UnitI
[1.1] - A57UnitI
[2]   - A57UnitL
[3]   - A57UnitM
[4]   - A57UnitS
[5]   - A57UnitW
[6]   - A57UnitX


Resource pressure per iteration:
[0]    [1.0]  [1.1]  [2]    [3]    [4]    [5]    [6]    
 -     3.00   3.00   7.00    -      -     20.50  3.50   

Resource pressure by instruction:
[0]    [1.0]  [1.1]  [2]    [3]    [4]    [5]    [6]    Instructions:
 -      -      -     1.00    -      -      -      -     ldp    s7, s16, [x24, #-8]
 -     0.50   0.50    -      -      -      -      -     add    x13, sp, #272

17、调试 --misched-only-func=foo --misched-only-block=9 指定函数foo的第9个BB不调度

18、 throughput是根據資源數量和指令時延算出來,参考M5WriteVSTI,如果没有定义则默认根据指令类型获取默认值(MCSchedule.h

SDIV的時延是 6-12 cycles, ProcResource 數量只有1, 所以throughput就該是 1/12 - 1/6
注意:mca可以通过如下方式设置分数,参考 https://lists.llvm.org/pipermail/llvm-dev/2015-April/084470.html

19、利用SchedWriteVariant来区分ADD/SUB指令针对是否有shift操作的时延不一致的问题,参考D8043

def : InstRW<[A57WriteISReg], (instregex ".*rs$")>; 也可以用

 def : SchedAlias<WriteISReg, A57WriteISReg>; 因为这些指令默认使用WriteISReg描述

注:默认值WriteISReg只能关联一次

注意:SchedAlias和WriteRes的区别参考https://lists.llvm.org/pipermail/llvm-commits/Week-of-Mon-20150406/270546.html

21、在SchedVariant中可以使用正则表达式,注RegShiftedPred是非随意的,rs$匹配的对应WriteISReg时序,未匹配的WriteI时序?疑问:未匹配的比如store指令为何没有用WriteI时序

此定义 llvm-tblgen AArch64.td -class=Instruction -I../../../include/ &> test.dump 对应的record

22、SchedWrite和SchedWriteRes的区别,参考TargetSchedule.td中的介绍

SchedWrite can be used inside AArch64InstrFormats.td for generalizing over a class of instructions.
SchedWriteRes allows you to fine-tune and override the pipeline and latency for a given instruction, or instructions matching a regex.(即只能用于InstRW或ItinRW的匹配)

23、shouldScheduleAdjacent定义两个指令需要被调度排布在一起,不定义是指令调度结束后分裂,参考D120104

24、D110480上有AMD等指令时序的描述

25、指令的Lantency在addVRegDefDeps中调用SchedModel.computeOperandLatency计算

26、finalizeBundle->prepend->..->bundleWithSucc设置了hasUnmodeledSideEffects(),从而访存指令不能跨过bundle( 可以为一类指令设置默认属性默认设置的属性仅对单指令的的tablegen pattern生效,参考D37097/D140680)

参考LLVM笔记(7) - 指令的side effect - Five100Miles - 博客园,对应side effect属性,指令默认存在副作用,需要let hasSideEffects = 0显示的调整),也可以参考 D139637 [AArch64][SVE][ISel] Combine dup of load to replicating load

utils/TableGen/InstrInfoEmitter.cpp:993:  if (Inst.hasSideEffects)     OS << "|(1ULL<<MCID::UnmodeledSideEffects)";

class SME_Switch_Intrinsic  --> 也可以pattern中设置
    : DefaultAttrsIntrinsic<[],[ llvm_i32_ty],[IntrNoMem, IntrHasSideEffects]>;

27、是否out of order模型的接口函数isOutOfOrder ==> 关键:MicroOpBufferSize

注意:在out-of-order 模型中,也可以通过let BufferSize = 0来设置确保Out Latency=1, 详见computeOutputLatency

def AnyLdSt : ProcResGroup<[UnitLdSt1, UnitLdSt2]> { let BufferSize = 0; }

28、Depth +  Height 在寄存器分配的调度后是一个常数?在寄存器分配前的调度中非常数;指令调度默认是misched-bottomup(因此无依赖的指令先调度,但实际是后发射),需要-mllvm -misched-topdown=true调整方向, 参考GenericScheduler::pickNodetryLatency

SU(0):   renamable $q1, renamable $q2 = LDPQi renamable $x9, -1 :: (load (s128) from %ir.scevgep10, align 8), (load (s128) from %ir.lsr.iv79, align 8)
  # preds left       : 0
  # succs left       : 4
  # rdefs left       : 0
  Latency            : 6  -> 
getCriticalCount(), SchedBoundary::countResource
  Depth              : 0  -> 
SU->getDepth(), tryLatency
  Height             : 17 -> 
SU->getHeight(), tryLatency

29、in-order时,Pending指已经消除依赖,但是时序未达ReadyCycle的指令?而Out-of-order时不需要考虑时序,仅考虑依赖?参考SchedBoundary::releaseNode

SchedBoundary::pickOnlyChoice中,如果checkHazard中检查发现指令发射数目超过uops, 也会重新将相关指令从Ready list中删除,放到Pend中==> 即使乱序模式下,也有Pend (dump中SU(1) uops=3

30、指令调度中dump信息解析,参考GenericScheduler::tryCandidate

** ScheduleDAGMILive::schedule picking next node  --> 开始应该新的group调度
Queue BotQ.P: 
Queue BotQ.A: 29 30 
  Cand SU(29) ORDER          --> 优选29,原因order, 即原来的指令顺序                    
  Cand SU(30) ORDER          --> 更新为30更优,同样原因是order                 
Pick Bot ORDER     
Scheduling SU(30) %52:zpr = LD1RD_IMM %16:ppr_3b,%51:gpr64common, 0

31、检查确认是否资源是瓶颈,参考SchedBoundary::countResource

  A57UnitL +2x6u  --> 前面2是Cycles,后面6是Factor

*** Critical resource A57UnitL: 2c

def : InstRW<[A57Write_6cyc_2L, WriteLDHi], (instrs LDPQi)>; ==> uops = 2+1=3,参考resolveSchedClass->getSchedClassDesc

34、 典型的SchedWriteRes描述,参考AArch64SchedA64FX.td

def A64FXWrite_ST2_WD_RI : SchedWriteRes<[A64FXGI0, A64FXGI56]> {
  let Latency = 11;
  let NumMicroOps = 3;         // 
  let ResourceCycles = [2, 2]; // 分别对应A64FXGI0A64FXGI56
}

A64FX中的注释 LDP only breaks into *one* LS micro-op

每个时钟周期同时占用一个A64FXGI0和一个A64FXGI56执行单元,等价描述

def A64FXWrite_ST2_WD_RI : SchedWriteRes<[A64FXGI0, A64FXGI0, A64FXGI56, A64FXGI56]> {
  let Latency = 11;
  let NumMicroOps = 3;
}

35、伪指令也是有定义的,比如LOADgot则关注后端对WriteAdrWriteLD的定义

def LOADgot : Pseudo<(outs GPR64common:$dst), (ins i64imm:$addr),
                     [(set GPR64common:$dst, (AArch64LOADgot tglobaladdr:$addr))]>,
              Sched<[WriteLDAdr]>;

def WriteLDAdr : WriteSequence<[WriteAdr, WriteLD]>;

也可以进行显示的定义,参考 AArch64SchedFalkorDetails.td

def : InstRW<[WriteSequence<[FalkorWr_1LD_3cyc, FalkorWr_1XYZ_1cyc]>],
                                      (instrs LOADgot)>;

36、InstRW定义中的顺序对指令的Lantency有影响,参考 D159254 [AArch64] Fix schedmodel pre/post-index loads and stores for Neoverse V2

def : InstRW<[WriteAdr, V2Write_5cyc_1I_3L, WriteLDHi],  -- 将地址更新更早发射

37、使用 llvm-mca -mtriple=aarch64 -mcpu=tsv110 --instruction-info=0 --resource-pressure=0 --timeline --timeline-max-iterations=1 可以展示指令流水线调度情况,参考gh #68854

   static const char Dispatched = 'D';
    static const char Executed = 'E';
    static const char Retired = 'R';
    static const char Waiting = '='; // Instruction is waiting in the scheduler.
    static const char Executing = 'e';
    static const char RetireLag = '-'; // The instruction is waiting to retire.

38、 基于PR80178可以看到Latency短的指令发射的优先级一般更低。

39、The main purpose of ReadAdvance is pipeline forwarding,可以通过-debug-only=subtarget-emitter调试?参考https://groups.google.com/g/llvm-dev/c/gbPGlDdT1Pc/m/lhHNPcMRBgAJ

bypass/forward指前后有两个指令,第一个指令的dst在执行完后还没有把结果写到寄存器之前,可以提前给到依赖于它的指令。

def ReadAdr : SchedReadAdvance<3, [WriteLD]> 代表来给写操作使用load的提前3拍发射

通过llvm自带脚本schedcover.py tblGenSubtarget.dbg 'default|NeoverseV1Model' 可以查看每个指令定义后的状态

注:上述tblGenSubtarget.dbg需要在cmake的命令中找到相关的生成命令,类似

   llvm-tblgen -DBSPRIV_AARCH64 -DBSPRIV_AARCH64 ... -o lib/Target/AArch64/AArch64GenSubtargetInfo.inc -d lib/Target/AArch64/AArch64GenSubtargetInfo.inc.d >$cwd/tblGenSubtarget.dbg 2>&1

40、指令调度处理融合情况 ,eg: ST.hasCmpBccFusion()

41、可以使用组合来定义Write_6c_1LD_2S

def Write_6c_1LD : SchedWriteRes<[UnitLD]> {
def Write_6c_1LD_2S : SchedWriteRes<[UnitLD, UnitS]> 则使用Write_6c_1LD_2S的地方可以使用def : InstRW<[Write_6c_1LD, WriteAdr]类似的组合替代

42、 commit 4a5b5bf展示对shift不同的常数定义不同Latency的方法,借助IsCheapLSL

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhongyunde/article/details/121652419

智能推荐

数据结构 图 最短路径-程序员宅基地

文章浏览阅读199次。(1)从某个源点到其余各顶点的最短路径Dijkstra的代码如下:头文件:#defineINFINITY10000#defineMAX_VERTEX_NUM20typedefintInfoType;typedefcharVertexType;typedefintVRType;typedefenum...{DG,DN,UDG,UDN}GraphKind;typedefstructA..._please input the number of vertex n

mysql主备配置文件 my.cnf_my.cnf配置的server_id-程序员宅基地

文章浏览阅读5.5k次。主:[mysqld]lower_case_table_names=1datadir=/mnt/mysql/datasocket=/mnt/mysql/mysql.socklog-error=/mnt/mysql/log/mysqld.logpid-file=/mnt/mysql/mysqld.pidwait_timeout=2147483interactive_timeout=2147483character_set_server=utf8init_connect='SET._my.cnf配置的server_id

堆排序(c语言)_堆排序加入一个节点-程序员宅基地

文章浏览阅读278次,点赞10次,收藏7次。堆排序的效率在各种排序中有比较高的效率,时间复杂度为O(nlog2n)_堆排序加入一个节点

sqoop import hbase by kerberos安全认证下_sqoop 脚本添加kerbores认证-程序员宅基地

文章浏览阅读515次。2020-07-15 16:30:47,365 ERROR tool.ImportTool: Encountered IOException running import job: java.io.IOException: Can't get Master Kerberos principal for use as renewer at org.apache.hadoop.mapreduce.security.TokenCache.obtainTokensForNamenodesIntern_sqoop 脚本添加kerbores认证

ubuntu sublime支持中文_ubuntu sublim 中文字体-程序员宅基地

文章浏览阅读779次。1. 安装fcitx中文输入法或sougou拼音输入法 * `sudo apt-get install fcitx-pinyin` * sougou + `sudo add-apt-repository ppa:fcitx-team/nightly` + `sudo apt-get update` 添加软件源,在使用之前要先更新_ubuntu sublim 中文字体

mysql物理分页逻辑分页_物理分页与逻辑分页的区别-程序员宅基地

文章浏览阅读385次。物理分页与逻辑分页的区别物理分页:物理分页就是护具看本身提供了分页方式,如MYSQL的limit、ORACLE的rownum,好处是效率高,不好的地方九江市不同的数据库有不同的搞法逻辑分页:利用游标分页,好处就是素有数据库都统一,好处就是效率低常用ORM框架采用的分页就是hibernate:采用的就是物理分页MyBatis使用RowBounds实现的分页是逻辑分页,也就是先把数据库记录全部查询出来..._mysql yspage是逻辑分页还是物理分页

随便推点

网络安全防护体系建设-程序员宅基地

文章浏览阅读8.9k次,点赞2次,收藏47次。构建适应数字化时代的网络安全防护体系,通过建立信任实现数字化业务的连接,通过控制风险抵御连接过程中的威胁,基于风险与信任的控制,保障网络安全防护体系落地。_网络安全防护体系

C语言—函数的引用_c语言引用函数-程序员宅基地

文章浏览阅读4k次。1.函数的定义 2.函数的返回语句 3.函数参数的应用 4.函数的调用_c语言引用函数

DA14531_写晶振校准值(xtrim)到OTP_14531 otp烧录-程序员宅基地

文章浏览阅读1.2k次。531写晶振校准值到OTP中开发过580/585的用户应该知道,想要将晶振校准值烧录到OTP中,我们只需往芯片的OTP Header中的某个固定的位置烧录一个两个字节的值即可。531的情况不太一样。DA14531的OTP Header分成了两部分: Configuration Script和Main group:其中,Main group存放一些常用的配置如:SPI FLASH 引脚,蓝牙 MAC 地址,时间戳等;特定的OTP位置位置,有其特定的含义。Configuration Script提供了比_14531 otp烧录

java调用内部action_Struts内部流程(总控ActionServlet读取Struts-config.xml后,将请求转发到诸负责业务处理的Action流程)模拟实现...-程序员宅基地

文章浏览阅读238次。我们知道,在Struts1.x中,大致的内部处理流程是这样的:ActionServlet作为中央处理器,它配置在Web.xml中,接受所有*.do的请求,然后解析URI,得到*.do中的*部分,即path,然后根据path在struts-config.xml中找到具体处理业务的Action以及与之配套的ActionForm和ActionForward,再根据Action的type用Java反射机制..._javal路径带有action=

HTML5引擎Construct2技术剖析(四)_construct2 随机数组-程序员宅基地

文章浏览阅读2.8k次。Construct2是一款跨平台的基于HTML5的游戏开发引擎,不需要编程(如果需要扩展插件,还是要会javascript),通过鼠标拖放组件和编辑事件表单(EventSheet)来完成的游戏开发。该引擎可以将开发的封装成多种形式,如phonegap、cocoonjs,导出浏览器、ios和android等平台应用。目前相关的中文资料还是比较少._construct2 随机数组

pycharm连接数据库mysql_MySQL数据库 -- Navicat、pycharm连接数据库-程序员宅基地

文章浏览阅读2.8k次,点赞2次,收藏23次。内容目录1.Navicat使用2.pycharm使用数据库一、Navicat使用#1. 测试+链接数据库#2. 新建库#3. 新建表,新增字段+类型+约束#4. 设计表:外键#5. 新建查询#6. 建立表模型#注意:批量加注释:ctrl+?键批量去注释:ctrl+shift+?键1.1 导出的sql语句代码代码附在最后1.2 导入数据库"""使用说明:拷贝上述代码,新建一个.sql文件,保存到桌面..._pycharm sql网页显示数据库