美文网首页
程序编译与代码优化

程序编译与代码优化

作者: ce5154e79490 | 来源:发表于2020-07-22 22:59 被阅读0次

概述

编译器是一段“不确定”的操作过程

编译器类型

  1. 前端编译器:将Java代码编译为class字节码
  • 代表:sun公司的javac(Java语言编写)、Eclipse JDT中的增量编译器ECJ
  1. 后端编译器(JIT编译器):将字节码转变为机器码
  • 代表: HotSpot VM的C1、C2编译器
  1. 静态提前编译器(AOT编译器 Ahead of Time Compiler):将Java代码编译为机器码
    -代表:GNU Compiler for the java、Excelsior JET

1. 早期(编译期)优化

主要说明Sun Javac的大概编译过程

  1. 解析与填充符号表过程
    1.1 解析:
    1.1.1 词法分析:将源代码的字符流转为标记(Token)集合(例如:关键字,变量名,运算符,字面量)
    1.1.2 语法分析:根据Token序列构造抽象语法树,语法树的每一个节点都是一个语法结构(例如:包,类型,修饰符,运算符,接口,返回值,代码注释)
    1.2 符号表填充:符号表由符号地址和符号信息组成的表格(可以看成K-V键值对)
  2. 注解处理
    插入式注解处理器的标准API在编译期间对注解进行处理
  3. 分析与生成字节码
    因为由于由语法分析所生成的抽象语法树不能保证逻辑性,故而语义分析是对正确性的审查
int a =1;
boolean b = false;
int c = a+b;//编译不能通过
  • 3.1 标注检查
    主要检查:变量使用前是否已经被声明、变量与赋值之间的数据类型是否能匹配
  • 3.2 数据及控制流分析
    主要检查:对程序的上下文更进一步的验证,如:局部变量在使用前是否已经赋值、方法的每条路径是否都有返回值、是否所有的受检异常都被正确的处理等问题
//这里的两个方法,编译出的字节码没有一点区别,只是有final修饰的不能被改变
public void test(final int a) {}
public void test(int a) {}
  • 3.3 解语法糖
    泛型擦除(实际上就是将结果强转)、变长参数、自动装箱/拆箱、内部类、枚举类、断言语句、对枚举和字符串的支持、try语句定义和关闭资源等
    条件编译:使用条件为常量的if语句
//1. 
public static void main (String[] args) {
    if (true) {
        System.out.println("1");
    } else {
         System.out.println("2");
      }
}
//在编译之后的结果
public static void main (String[] args) {
    System.out.println("1");   
}
//2. 下面语句将会拒绝编译
public static void main(String[] args) {
        while (false) {
            System.out.println("错误");
        }
    }
  1. 字节码生成
    字节码生成不仅仅将前面步骤生成的信息转换为字节码写到磁盘中,还要进行少量的代码添加和转换工作

2. 晚期(运行期)优化

部分商用虚拟机(Sun HotSpot、IBM J9)中,Java程序最初通过解释器(interpreter)解释执行,当虚拟机发现某个方法或者代码运行特别频繁时,就会把这些代码定义为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化。完成这个任务的编译器就是即时编译器(JIT just int time complier)
JRockit没有解释器,因此启动时间较长

  1. 为何HotSpot 虚拟机要使用解释器与编译器并存的架构?
    两种编译器各有优势: 解释器:启动快、执行快; 编译器:执行效率高
    Client模式启动速度较快,Server模式启动较慢,但是启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多
  2. 为何HotSpot 虚拟机要实现两个不同的即使编译器?
    client compiler:获取更快的编译速度;server compiler:获取更好的编译质量。具体使用哪种,虚拟机会根据自身版本和宿主机的硬件性能自由选择,当然也可以强制运行某种模式,无论编译器采用client compiler 还是server compiler,解释器与编译器搭配使用都称为“混合模式(mixed mode)”,也可以强制虚拟机运行编译模式(-Xcomp)或者解释模式(-Xint)
MacBook-Pro:~ shizhenshuang$ java -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)
MacBook-Pro:~ shizhenshuang$ java -Xint -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, interpreted mode)
MacBook-Pro:~ shizhenshuang$ java -Xcomp -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, compiled mode)
  1. 程序什么时候用解释器执行?什么时候用编译器执行?
    当程序刚开始启动的时候,解释器优先执行,省去编译时间,当程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码。那么,什么时候编译器开始执行呢?

编译对象(热点代码)与触发条件

  • 被多次调用的方法
  • 被多次调用的循环体(实际编译的事整个方法)
    方法替换使用的是栈上替换(On Stack Replacement)

判断一段代码是不是热点代码,是否需要触发即时编译,这样的行为成为热点探测。热点探测目前流行的有两种方法

  1. 基于采样的热点探测
    虚拟机周期性检查各个线程的栈顶,如果出现某个(或某些)方法经常出现在栈顶,那就是热点方法
  2. 基于计数器的热点探测
    虚拟机为某个方法或者代码块建立计数器,统计执行次数,如果执行次数超过一定的阈值就认为它是热点代码。(阈值:client模式是1500,server模式是10000,可以通过参数:-XX:CompileThreshold 设置)
    计数器又分为:
    2.1 方法调用计数器
方法调用计数器触发即时编译

2.2 回边计数器

回边计数器触发编译条件
  1. 如何从外部观察即时编译器编译过程和编译结果?
    使用debug,fastdebug版本的虚拟机(JDK6u25之后就不提供下载了),运行时,添加参数-XX:+PrintCompilation
  2. 编译优化项
    代表
  • 1 方法内联:1. 除去方法调用的成本;2. 为其他优化建立良好的基础
class A {
    int age;

    public int getAge() {
        return age;
    }
}
    public static void main(String[] args) {
        A a = new A();
        int y = a.getAge();
    }
//优化后的代码如下(用Java代码表示)
  public static void main(String[] args) {
        A a = new A();
        int y = a.age;
    }
  • 2 消除冗余代码
  • 3 代码复写传播
int y = 2;
z = y;
int sum = y+ z;
//复写传播
y=y;
int sum = y+y;
//消除冗余代码后
int sum = y+y;

典型代表
1. 公共子表达式消除:如果一个表达式E已经计算过了,并且从先前的计算到现在E中的所有变量的值都没有改变,那么E的这次出现就成为了公共子表达式,也就没必要再花时间对他进行计算了(若a+b=c, 则int i = a+b+1 --> int i = c+1)
2. 数组范围检查消除:1. 编译时检查,2. 通过数据流分析
3. 方法内联:就是把目标方法的代码拷贝到调用方,避免发生真实的方法调用
4. 逃逸分析(JDK1.6开始):它并不是直接优化代码的手段,而是为其他优化手段提供依据的分析技术。逃逸分析的基本行为就是分析对象动态作用域。当一个对象在方法中被定义,通过参数的形式传递到其他方法中,称为方法逃逸。甚至有可能被外部线程访问到,譬如赋值给类变量或在其他线程中访问实例变量,称为线程逃逸。如果能够确定一个对象不会逃逸到方法或者线程之外,则可以为这个对象做一些高效的优化
4.1 栈上分配:将对象分配在栈上,内存空间随着栈帧出栈而销毁,减少gc回收的压力
4.2 同步消除:如果确定一个对象不会逃逸出线程,就无须对这个对象实施通过的措施(线程同步是一个相对耗时的过程)
4.3 标量替换:标量是指不能再拆解的数据类型,如原始数据类型。如果一个数据还能被分解,它就称为聚合量,如对象。标量替换就是将对象的成员变量替换为原始的数据类型。逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆解,那么程序执行的时候可能就不会真正的创建这个对象,而是创建它的若干个被这个方法访问的成员变量

  1. Java与C++的编译器对比
    即:即时编译器与静态编译器的对比
    1. 即时编译器占用的是用户运行的时间,具有很大的时间压力。而编译时间成本在静态编译器中并不是主要关注点
    1. Java语言是动态类型安全语言。这就意味着由虚拟机来确保程序不会违反语义和非结构化内存,这就使得虚拟机得频繁的动态检查空指针,数组下标范围,类型转换等
    1. Java中虽然没有virtual关键字,但是接受者进行动态选择的频率要远远大于C/C++语言,优化难度要大于静态编译器
    1. Java语言是动态扩展语言,运行时加载新的类可能会改变程序类型的继承关系
    1. Java语言中的对象都是在堆上分配,只有方法中的局部变量才在栈上分配。而C/C++则有多种内存分配,既可以在堆上,也可以在栈上。C/C++主要是用户程序代码回收内存分配,因此效率上要高于Java

相关文章

  • 深入理解Java虚拟机读书笔记(三)

    程序编译与代码优化 1. 编译期优化 1.1 概述 有三种编译: 1.前端编译器,javac,把.java文件转化...

  • 程序编译与代码优化

    概述 编译器是一段“不确定”的操作过程 编译器类型 前端编译器:将Java代码编译为class字节码 代表:sun...

  • 程序编译与代码优化

    早期(编译期)优化 编译器类型 前端编译器:把java文件变成class文件;比如我们的idea,javac等。(...

  • 程序性能之编译优化

    在用编译型语言写程序时,编译器会对程序员的代码进行优化。这是否意味着程序员在写高层代码时,无需关心程序性能将优化工...

  • 第一章绪论

    编译过程和编译程序结构 五个阶段: 词法分析 语法分析 语义分析和中间代码生成 优化 目标代码生成 编译程序的开发...

  • 要点提炼| 理解JVM之程序编译&代码优化

    本篇将介绍程序编译时期的代码优化手段,分成两个阶段: 概述 早期(编译期)优化 晚期(运行期)优化 1.概述 a....

  • GCC编译优化

    编译优化 如果不指定优化标志,gcc会产生可调试的代码 启用优化后,gcc会改变程序的结构,让代码变小或者运行得更...

  • Java学习-程序编译与代码优化

    介绍 java代码编译器代表性的有三类前端编译器:我们熟知的javac就是前端编译器JIT编译器:即时编译器,如h...

  • 程序编译和代码优化

    早期(编译期)优化 java泛型 在java中,泛型方法在编译之后,生成的class文件里面保存的是原生类型。 晚...

  • 2017阿里巴巴面试技术挑战题泄露

    1、编译程序的前3个阶段完成的工作是:【单选】A:词法分析、语法分析和代码优化B:代码生成、代码优化和词法分析C:...

网友评论

      本文标题:程序编译与代码优化

      本文链接:https://www.haomeiwen.com/subject/hwbtlktx.html