Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • ☕ Understanding the JVM
    • Part 2 - 自动内存管理

      • Chapter 2 - Java 内存区域与内存溢出异常
      • Chapter 3.1-3.2 - 概述 && 对象已死
      • Chapter 3.3-3.4 - 垃圾收集算法 && HotSpot 的算法实现细节
      • Chapter 3.5 - 经典垃圾收集器
      • Chapter 3.6 - 低延迟垃圾收集器
    • Part 3 - 虚拟机执行子系统

      • Chapter 6 - 类文件结构
      • Chapter 7 - 虚拟机类加载机制
      • Chapter 8.1-8.2 - 运行时栈帧结构
      • Chapter 8.3 - 函数调用
      • Chapter 8.5 - 基于栈的字节码解释执行引擎
      • Chapter 9.2 - Tomcat: 正统的类加载器架构
    • Part 4 - 程序编译与代码优化

      • Chapter 10 - 前端编译与优化
      • Chapter 11.2 - 即时编译器
      • Chapter 11.3-11.4 - 提前编译器 && 编译器优化技术
    • Part 5 - 高效并发

      • Chapter 12.3 - Java 内存模型
      • Chapter 12.4-12.5 - Java 线程与协程
      • Chapter 13 - 线程安全与锁优化

Chapter 11.3-11.4 - 提前编译器 && 编译器优化技术

Created by : Mr Dk.

2020 / 02 / 02 22:29 🧨🧧

Ningbo, Zhejiang, China


11.3 提前编译器

11.3.1 提前编译的优劣得失

提前编译 (Ahead Of Time, AOT) 在 Java 中的价值直指即时编译的最大弱点:即时编译需要占用运行时资源。比如,编译时最耗时的优化措施是进行 过程间分析,必须在全程序范围内做大量耗时计算工作。目前常见的 JVM 都是通过大规模的函数内联 + 过程内分析来模拟过程间分析。提前编译的本质是给即时编译器做缓存加速,改善 Java 启动后需要一段时间的预热才能到达最高性能的问题。

提前编译不仅要和目标机器相关,还需要与 JVM 运行时的参数绑定。即时编译器相对于提前编译器的天然优势:

  1. 性能分析制导优化 (Profile-Guided Optimization, PGO)
    • 解释器和客户端编译器会不断收集性能监控信息
    • 这些信息在静态分析时是无法得到的
    • 动态运行时可以看出其偏好性
  2. 激进预测性优化 (Aggressive Speculative Optimization)
    • 性能监控信息能够支持一些正确可能性很大的预测,就可以按高概率的假设进行优化
    • 万一真的出现了错误预测,大不了退回到低级编译器甚至解释器上执行
    • 只要出错的概率足够低,这样的优化就能大幅降低目标复杂度
  3. 链接时优化 (Link-Time Optimization, LTO)
    • Java 语言天生就是动态链接的
    • C/C++ 的主程序与动态链接库在编译时完全独立,不能联合优化

11.4 编译器优化技术

11.4.2 方法内联

把目标代码原封不动地复制到发起调用的代码中,避免发生真实的函数调用。JVM 引入了 类型继承关系分析 (Class Hierarchy Analysis, CHA) 技术,确定类型之间的关系

  • 对于非虚函数,可以直接内联
  • 对于虚函数
    • 如果 CHA 只查询到只有一个目标版本,则进行 守护内联 (Guarded Inlining)
    • 由于 Java 的动态链接性,如果有新的类型加载,改变了 CHA 的结论,则发生 激进预测性优化,需要放弃已经编译的代码,回退到解释器或重新编译
    • 如果 CHA 确认有多个版本的目标函数可供选择,使用内联缓存来缩减函数调用开销
      • 缓存建立在目标函数的入口之前
      • 缓存记录函数接收者的类型,并在每次函数调用时都比较接收者的版本
      • 如果每次接收者都是一样的,则是单态内联缓存
      • 如果出现了接收者不一致,则退化为超多态内联缓存,相当于查虚函数表进行函数分派

11.4.3 逃逸分析 (Escape Analysis)

分析对象的动态作用域,划分逃逸程度:

  • 从不逃逸
  • 函数逃逸 - 对象在函数中被定义后,可能作为参数被外部函数引用
  • 线程逃逸 - 对象会被外部线程访问到

如果一个对象逃逸程度较低,就可能进行不同程度的优化:

  • 栈上分配 (Stack Allocation)
    • 确定一个对象不会逃逸出线程,那么可以在栈上为其分配内存,而不是在 Java Heap 上
    • 对象占用的内存随栈帧出栈而被销毁
    • 从不逃逸和函数逃逸的对象占比很高,大量对象会随着函数结束而自动销毁,GC 子系统压力下降
    • 不支持线程逃逸
  • 标量替换 (Scalar Replacement)
    • 标量指不可再分为更小数据的数据,比如原始数据类型
    • 将对象用到的成员变量恢复为原始类型来访问
    • 程序执行时可以不创建对象,只创建使用到的成员即可
    • 栈上分配 + 后续优化
    • 不支持函数逃逸
  • 同步消除 (Synchronization Elimination)
    • 如果确定变量不会线程逃逸,对变量的所有同步措施可以安全地消除掉

逃逸计算的成本非常高,且不能保证逃逸分析带来的性能收益会高于消耗。所以 JVM 中只能采用不那么准确,但时间压力较小的算法。逃逸分析可能出现效果不稳定的情况,或分析过程耗时却无法辨别出非逃逸对象,导致性能下降。

11.4.4 公共子表达式消除

不重复计算已经计算过的表达式。

11.4.5 数组边界检查消除

在 Java 中会对数组访问自动进行上下界的范围检查,因此每次数组元素的读写都带有一次隐含的条件判定操作。对于拥有大量数组访问的程序,是一种性能负担。对于可以确定合法范围的数组访问,尽可能将运行期检查提前到编译期内完成,可以省掉数组的上下界检查。

Edit this page on GitHub
Prev
Chapter 11.2 - 即时编译器