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

本文关键字:字节 优化 编译器 何积极 具体来说 | 更新日期: 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"