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

时间:2012-06-17 15:39:49

标签: c# flash optimization compiler-construction llvm

我一直在阅读各种编译器的功能,并且我遇到了许多编译器据报道要执行的“积极优化”这个术语。例如,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"

3 个答案:

答案 0 :(得分:4)

我也一直致力于这个,transformations that LLVM performs的完整列表,在标题下组织:

  • 删除死代码
    • 激进的死法代码消除
    • 死码消除
    • 死亡论据消除
    • 死亡类型消除
    • 死亡教学消除
    • Dead Store Elimination
    • 死亡全球消除
    • 删除死循环
  • 不需要的数据删除
    • 从模块中删除所有符号
    • 剥离未使用符号的调试信息
    • 剥离未使用的函数原型
    • 剥离所有llvm.dbg.declare intrinsics
    • 从模块中删除除dbg符号之外的所有符号
    • 合并重复的全局常量
    • 删除未使用的异常处理信息
  • 内联函数
    • 合并功能
    • Partial Inliner
    • 功能整合/内联
  • 循环优化
    • Loop-Closed SSA表格通行证
    • Loop Invariant Code Motion
    • 将循环提取到新函数
    • 最多将一个循环提取到新函数
    • 环路强度降低
    • 旋转循环
    • Canonicalize natural loops
    • 展开循环
    • 取消开关循环
    • 向scalars推广'by reference'参数
    • 将指令组合在基本块中形成向量指令
    • 配置文件引导基本块放置
    • 打破CFG中的关键边缘
    • 优化代码生成
    • 简单的常量传播
    • 演绎功能属性
    • 全局变量优化工具
    • 全球价值编号
    • Canonicalize Induction Variables
    • 插入边缘剖析仪器
    • 插入边缘剖析的最佳仪器
    • 合并冗余指令
    • 内部化全球符号
    • 过程间常数传播
    • 过程间稀疏条件常数传播
    • 跳线程
    • 将原子内在性降低到非原子形式
    • 降低调用和展开,适用于不放松的代码生成器
    • 将SwitchInst降低到分支机构
    • 促进注册记忆
    • MemCpy优化
    • 统一功能退出节点
    • 重新关联表达式
    • 将所有值降级为堆栈插槽
    • 标量替换聚合物(DT)
    • 稀疏条件常数传播
    • 简化众所周知的图书馆电话
    • 简化CFG
    • 代码下沉
    • 提升多个ret值的sret参数
    • 尾巴呼唤消除
    • 尾部复制

答案 1 :(得分:2)

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

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

答案 2 :(得分:1)

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

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"