具体来说,编译器如何积极优化生成的字节码?
我一直在阅读各种编译器的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 —循环增加了一个决定是否进入循环的条件
- 循环展开 —循环由循环体的复制副本替换
- 函数内联 —函数调用被函数体的副本替换