具体来说,编译器如何积极优化生成的字节码
本文关键字:字节 优化 编译器 何积极 具体来说 | 更新日期: 2023-09-27 18:37:24
我一直在阅读各种编译器的功能,并且我遇到了许多编译器报告的术语"积极优化"。例如,LLVM引用了以下编译时优化功能:
- 内存/指针特定
- 循环转换
- 数据流
- 算术
- 死代码消除
- 内联
这具体意味着什么?假设您有以下代码片段,如何优化生成的字节代码以比编译器生成的运行速度更快?我对优化 JIT 驱动的运行时(如 C#、Java 和 Flash)的字节码特别感兴趣。这很棘手,因为 JIT 仅支持处理器通常执行的操作码子集,这限制了您可以执行的优化量。不过,我还是很想知道有什么可能,以及究竟哪些转换可以突破 VM 的极限。
虚构的代码块:
for (i = 0; i < 100; i++){
in = dataIn[i];
if ((in % 5) == 0){
out = ((in / 2) >> 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 < 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
// out = ((in / 2) >> 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"
我也一直在研究这个,LLVM 执行的转换的完整列表,组织在标题下:
-
死代码删除
- 积极的死代码消除
- 死代码消除
- 死参数消除
- 死型消除
- 死指令消除
- 死区消除
- 死亡全球消除
- 删除死循环
-
删除不需要的数据
- 从模块中剥离所有符号
- 去除未使用符号的调试信息
- 剥离未使用的功能原型
- Strip all llvm.dbg.declare intrisics
- 从模块中删除除 dbg 符号之外的所有符号
- 合并重复的全局常量
- 删除未使用的异常处理信息
-
内联函数
- 合并函数
- 部分内衬
- 功能集成/内联
-
循环优化
- 循环闭合 SSA 表单通行证
- 循环不变代码运动
- 将循环提取到新函数中
- 最多将一个循环提取到一个新函数中
- 环路强度降低
- 旋转循环
- 规范化自然循环
- 展开循环
- 取消切换环路
-
杂项
- 将"通过引用"参数提升为标量
- 组合指令以在基本块内形成向量指令
- 剖面引导的基本块放置
- 打破 CFG 中的关键边缘
- 针对代码生成进行优化
- 简单的常数传播
- 推导函数属性
- 全局变量优化器
- 全局值编号
- 规范化归纳变量
- 插入用于边缘轮廓的仪器
- 插入用于边缘分析的最佳仪器
- 组合冗余指令
- 内化全局符号
- 过程间常量传播
- 过程间稀疏条件常量传播
- 跳跃线程
- 将原子内禀函数降低到非原子形式
- 较低的调用和解展开,适用于无卷代码生成器
- 将交换机下到分支
- 提升内存以注册
- 内存优化
- 统一函数出口节点
- 重新关联表达式
- 将所有值降级到堆叠槽
- 聚合体的标量替换 (DT)
- 稀疏条件常量传播
- 简化已知库调用
- 简化 CFG
- 代码下沉
- 将 ret 参数提升为多个 ret 值
- 消除尾叫
- 尾部复制
虽然这不能回答你的问题,但我遇到了C++编译器为优化生成的机器代码而执行的以下转换:
- 强度降低 用作数据索引---迭代变量以与数据单元大小匹配的速率递增
- 隐藏参数表---返回结构的函数,实际上将其写入隐藏参数指向的区域
- 整数除法 --- 某些 fornulas 可用于在已知除数的情况下更有效地计算整数除法
- 浮点条件 ---浮点条件变成复杂的指令序列 设置和查询浮点状态
- 复杂数学---复杂的乘法或除法转换为库调用
- --- memcpy()、memset()、strcmp() 或 strlen() 操作的本机例程被转换为 rep mov、rep sto、rep zcmp 或 rep zscas
- 短路 ---在基本块树中评估复杂条件
- 工会歧义 --- 有关工会哪个成员的信息丢失
- 逐字复制较大的双精度值或聚合值---复制碎片
- 测试碎片 ---长整数值的条件由对该值的各个单词的单独测试组成
- 开关碎片 --- switch 语句被值上的条件嵌套所取代 循环
- 标头复制 循环---增加一个条件,该条件决定是否进入循环
- 循环展开---循环被循环主体的复制副本替换
- 函数内联---函数调用替换为函数主体的副本
以下是编译器可以进行的两个简单优化:
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"