具体来说,编译器如何积极优化生成的字节码?

我一直在阅读各种编译器的function,并且我遇到了许多编译器报告执行的“积极优化”一词。 例如,LLVM引用了以下编译时优化function:

  • 内存/指针特定
  • 循环变换
  • 数据流
  • 算术
  • 死代码消除
  • 内联

具体是什么意思? 假设您有以下代码片段,如何优化生成的字节代码以比编译器生成的更快地运行? 我特别感兴趣的是优化JIT驱动的运行时的字节码,例如C#,Java和Flash。 这很棘手,因为JIT只支持处理器通常执行的操作码的子集,这限制了您可以执行的优化量。 尽管如此,我仍然有兴趣看到什么是可能的,究竟是什么转换可以推动VM的极限。

虚构的代码块:

for (i = 0; i > 16) - 10; }else{ out = ((in << 5) / 2) * 50 + 10; } dataOut[i] = out; } 

编译器为基于堆栈的JIT VM(如Flash Player)生成的近似伪代码:(原谅我任何错误,这完全是手写的!)

 // i = 0 label: "forInit" push 0 writeTo "i" // while i > 16) - 10; push "in" push 2 divide push 16 rightshift push 10 minus writeTo "out" goto "ifEnd" // else label: ifPart2 // out = ((in << 5) / 2) * 50 + 10; push "in" push 5 leftshift push 2 divide push 50 multiply push 10 add writeTo "out" // dataOut[i] = out; label: ifEnd push "out" push "i" push "dataOut" writeProp // i++ push "i" increment writeTo "i" // while i < 100 goto "forStart" label: "forEnd" 

以下是编译器可以进行的两个简单优化:

 out = ((i / 2) >> 16) - 10; 

可以减少到

 out = (i >> 17) - 10; 

 out = ((i << 5) / 2) * 50 + 10; 

可以减少到

 out = (i << 4) * 50 + 10; 

回答你的问题“如何优化生成的字节代码以比编译器生成的更快的速度运行?” 这是字节码的另一个版本,它有一些优化。

 // i = 0 label: "forInit" push 0 writeTo "i" // while i < 100 label: "forStart" push "i" push 100 jumpIfMoreThan "forEnd" // in = dataIn[i]; push "i" push "dataIn" readProp saveTo "in" // if ((in % 5) == 0) push "in" push 5 mod push 0 jumpIfNotEquals "ifPart2" label: ifPart1 // optimization: remove unnecessary /2 // out = ((in / 2) >> 16) - 10; push "in" push 17 rightshift push 10 minus // optimization: don't need out var since value on stack // dataOut[i] = out; push "i" push "dataOut" writeProp // optimization: avoid branch to common loop end // i++ push "i" increment writeTo "i" goto "forStart" // else label: ifPart2 // optimization: remove unnecessary /2 // out = ((in << 5) / 2) * 50 + 10; push "in" push 4 leftshift push 50 multiply push 10 add // optimization: don't need out var since value on stack // dataOut[i] = out; push "i" push "dataOut" writeProp // optimization: avoid branch to common loop end // i++ push "i" increment writeTo "i" goto "forStart" label: "forEnd" 

我也一直在研究这个, LLVM执行的完整转换列表,在标题下组织:

  • 死代码删除
    • 积极的死代码消除
    • 死代码消除
    • 死论据消除
    • 死亡类型消除
    • 死教学消除
    • 死店消除
    • 死亡的全球消除
    • 删除死循环
  • 不需要的数据删除
    • 从模块中剥离所有符号
    • 剥去未使用符号的调试信息
    • 剥离未使用的函数原型
    • 剥去所有llvm.dbg.declare内在函数
    • 从模块中删除除dbg符号之外的所有符号
    • 合并重复的全局常量
    • 删除未使用的exception处理信息
  • 内联函数
    • 合并function
    • 部分内联
    • function集成/内联
  • 循环优化
    • 循环封闭的SSA表格通行证
    • 循环不变代码运动
    • 将循环提取到新函数中
    • 最多将一个循环提取到一个新函数中
    • 环强度降低
    • 旋转循环
    • 规范化自然循环
    • 展开循环
    • 无开关循环
  • 杂项
    • 向scalars推广“引用”参数
    • 组合指令以在基本块内形成向量指令
    • 配置文件引导基本块放置
    • 打破CFG中的关键边缘
    • 优化代码生成
    • 简单的恒定传播
    • Deduce函数属性
    • 全局变量优化器
    • 全球价值编号
    • 规范化感应变量
    • 插入用于边缘轮廓分析的检测
    • 插入用于边缘轮廓分析的最佳仪器
    • 结合冗余指令
    • 内化全球符号
    • 过程间常数传播
    • 过程间稀疏条件常数传播
    • 跳线程
    • 将primefaces内在性降低到非primefacesforms
    • 较低的调用和展开,适用于不放松的代码生成器
    • 将SwitchInst降低到分支机构
    • 促进记忆注册
    • MemCpy优化
    • 统一function退出节点
    • 重新关联表达式
    • 将所有值降级为堆栈插槽
    • 标量替换聚合(DT)
    • 稀疏条件常数传播
    • 简化着名的库调用
    • 简化CFG
    • 代码下沉
    • 提升多个ret值的sret参数
    • 尾巴呼叫消除
    • 尾部重复

虽然这没有回答你的问题,但我遇到了C ++编译器为优化生成的机器代码而执行的以下转换:

  • 强度降低 —用作数据索引的迭代变量以与数据单元大小匹配的速率递增
  • 隐藏参数 —返回结构的函数实际上将其写入隐藏参数指向的区域
  • 整数除法 – 在已知除数的情况下,某些套管可用于更有效地评估整数除法
  • 浮动条件 —浮点条件变为复杂的指令序列设置和查询浮点状态
  • 复杂数学 —复杂的乘法或除法变成了库调用
  • 本机例程 —将memcpy(),memset(),strcmp()或strlen()操作转换为rep mov,rep sto,rep zcmp或rep zscas
  • 短路 – 复杂条件在基本块树中进行评估
  • Union Ambiguation —关于工会的哪个成员的信息丢失了
  • 复制碎片 —大的双重或聚合值是逐字复制的
  • 测试碎片 —长整数值的条件由对该值的单个单词的单独测试组成
  • Switch Fragmentation —一个switch语句被一个值上的嵌套条件替换
  • Loop Header Copy —循环增加了一个决定是否进入循环的条件
  • 循环展开 —循环由循环体的复制副本替换
  • 函数内联 —函数调用被函数体的副本替换