性能优化:JIT即时编译与AOT提前编译

news2024/11/18 13:58:35

优质博文:IT-BLOG-CN

一、简介

JITAOT的区别: 两种不同的编译方式,主要区别在于是否处于运行时进行编译。

JIT:Just-in-time动态(即时)编译,边运行边编译:在程序运行时,根据算法计算出热点代码,然后进行JIT实时编译,这种方式吞吐量高,有运行时性能加成,可以跑得更快,并可以做到动态生成代码等,但是相对启动速度较慢,并需要一定时间和调用频率才能触发JIT的分层机制。JIT缺点就是编译需要占用运行时资源,会导致进程卡顿。

AOT:Ahead Of Time指运行前编译,预先编译:AOT编译能直接将源代码转化为机器码,内存占用低,启动速度快。无需runtime运行,直接将runtime静态链接至最终的程序中,但无运行时性能加成,不能根据程序运行情况做进一步的优化AOT缺点就是在程序运行前编译会使程序安装的时间增加。

JIT即时编译指的是在程序的运行过程中,将字节码转换为可在硬件上直接运行的机器码,并部署至托管环境中的过程。而AOT编译指的则是,在程序运行之前,便将字节码转换为机器码的过程。

.java -> .class -> (使用jaotc编译工具) -> .so(程序函数库,即编译好的可以供其他程序使用的代码和数据)

在这里插入图片描述

二、JIT

HotSpot虚拟机中,内置了两种JIT,分别为C1编译器和C2编译器,这两个编译器的编译过程是不一样的。

【1】C1编译器:C1编译器是一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序,也称为Client Compiler
【2】C2编译器:C2编译器是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序,也称为Server Compiler,例如,服务器上长期运行的Java应用对稳定运行就有一定的要求。JDK 6开始定义服务器级别的机器是至少有两个CPU2GB的物理内存,才开启C2
【3】分层编译:Java8中,默认开启分层编译,在1.8之前,分层编译默认是关闭的。在Java7之前,需要根据程序的特性来选择对应的JIT,虚拟机默认采用解释器和其中一个编译器配合工作。

分层编译将JVM的执行状态分为了5个层次:
【1】第 0 层:程序解释执行,默认开启性能监控功能Profiling,如果不开启,可触发第二层编译;
【2】第 1 层:可称为C1编译,将字节码编译为本地代码,进行简单、可靠的优化,不开启Profiling
【3】第 2 层:也称为C1编译,开启Profiling,仅执行带方法调用次数和循环回边执行次数profilingC1编译;
【4】第 3 层:也称为C1编译,执行所有带ProfilingC1编译;
【5】第 4 层:可称为C2编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

mixed mode代表是默认的混合编译模式,除了这种模式外,我们还可以使用-Xint参数强制虚拟机运行于只有解释器的编译模式下,这时JIT完全不介入工作;也可以使用参数-Xcomp强制虚拟机运行于只有JIT的编译模式下。如下:

如果只想开启C2,可以关闭分层编译-XX:-TieredCompilation,如果只想用C1,可以在打开分层编译的同时,使用参数:-XX:TieredStopAtLevel=1

C1、C2C1+C2,分别对应clientserver和分层编译。C1编译速度快,优化方式比较保守;C2编译速度慢,优化方式比较激进。C1+C2在开始阶段采用C1编译,当代码运行到一定热度之后采用G2重新编译。

参数-XX:ReservedCodeCacheSize = N(其中N是为特定编译器提供的默认值)主要设置热点代码缓存codecache的大小。如果缓存不够,则JIT无法继续编译,并且会去优化,比如编译执行改为解释执行,由此,性能会降低。同时,可以通过java -XX:+PrintCodeCache查看codecache的使用情况:

C:/Users/Administrator> java -XX:+PrintCodeCache
CodeCache: size=245760Kb used=1165Kb max_used=1165Kb free=244594Kb
 bounds [0x000000010be1b000, 0x000000010c08b000, 0x00000011ae1b000]
 total_blobs=293 nmethods=48 adapters=159
 compilation: enable

相关参数:

参数默认值说明
-XX:InitialCodeCacheSize2555904(240M)默认的CodeCache区域大小,单位为字节
-XX:ReservedCodeCacheSize251658240(240M)CodeCache区域的最大值,单位为字节
-XX:CodeCacheExpansionSize65536(64K)CodeCache每次扩展大小,单位为字节
-XX:ExitOnFullCodeCachefalseCodeCache区域满了的时候是否退出JVM
-XX:UseCodeCacheFlushingfalse是否在关闭JIT编译前清除CodeCache
-XX:MinCodeCacheFlushingInterval30刷新CodeCache的最小时间间隔 ,单位为秒
-XX:CodeCacheMinimumFreeSpace512000CodeCache区域的剩余空间小于参数指定的值时停止JIT编译。剩余的空间不会再用来存放方法的本地代码, 可以存放本地方法适配器代码
-XX:CompileThreshold10000指定方法在在被JIT编译前被调用的次数
-XX:OnStackReplacePercentage140该值为用于计算是否触发OSR(OnStackReplace)编译的阈值

如何判断热点代码

【1】基于采样的热点探测: 主要是虚拟机会周期性的检查各个线程的栈顶,若某个或某些方法经常出现在栈顶,那这个方法就是“热点方法”。优点是实现简单。缺点是很难精确一个方法的热度,容易受到线程阻塞或外界因素的影响。
【2】基于计数器的热点探测(典型应用-Hotspot): 主要就是虚拟机给每一个方法甚至代码块建立了一个计数器,统计方法的执行次数,超过一定的阀值则标记为此方法为热点方法。Hotspot使用的基于计数器的热点探测方法。然后使用了两类计数器:方法调用计数器和回边计数器。当方法计数器和回边计数器之和超过方法计数器阈值时,就会触发JIT编译器。
【3】方法调用计数器: 方法调用计数器用于统计方法被调用的次数,默认阈值在C1模式下是1500次,在C2模式在是10000次,可通过-XX: CompileThreshold来设定;而在分层编译的情况下-XX: CompileThreshold指定的阈值将失效,此时将会根据当前待编译的方法数以及编译线程数来动态调整。
【4】回边计数器: 回边计数器用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”Back Edge,该值用于计算是否触发C1编译的阈值,在不开启分层编译的情况下,C1默认为13995C2默认为10700,可通过-XX: OnStackReplacePercentage=N来设置;而在分层编译的情况下,-XX: OnStackReplacePercentage指定的阈值同样会失效,此时将根据当前待编译的方法数以及编译线程数来动态调整。

回边计数器阈值计算规则:
1、C1模式下:CompileThreshold*OnStackReplacePercentage/100;即:方法调用计数器阈值*OSR比率/100;
2、C2模式下:(CompileThreshold)*(OnStackReplacePercentage-InterpreterProfilePercentage)/100;即:方法调用计数器阈值*(OSR比率 - 解释器监控比率)/100;

JIT优化

JIT编译运用了一些经典的编译优化技术来实现代码的优化:主要有两种:方法内联逃逸分析

【1】方法内联: 方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用。方法内联不仅可以消除调用本身带来的性能开销,还可以进一步触发更多的优化。

    private int add1(int s1, int s2, int s3, int s4) {
        return add2(s1+s2) + add2(s3+s4);
    }

    private int add2(int s1, int s2) {
        return s1+s2;
    }	

    // 方法内联后的代码
    private int add(int s1, int s2, int s3, int s4) {
        return s1+s2+s3+s4;
    }

提高方法内联的策略:调整参数;写小方法;使用staticfinal关键字,不用出现方法继承,没有额外的类型检查,就可能发生内联;

【2】锁消除: 如果是在单线程环境下,JIT编译会对这个对象的方法锁进行锁消除,jdk1.8默认开启。例如:

    //-XX:-EliminateLocks 先关闭锁消除, 再打开, 执行此段代码100万次查看差别很大
    public static String getString(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
    }

【3】标量替换: 逃逸分析证明一个对象不会被外部访问,如果这个对象可以被拆分的话,当程序真正执行的时候可能不创建这个对象,可以直接创建它的成员变量来代替,前提要开启逃逸分析(jdk1.8默认开启逃逸分析-XX:+DoEscapeAnalysis; -XX:+EliminateAllocations开启标量替换jdk1.8默认开启)。

public void foo() {
    Person info = new Person ();
    info.name = "queen";
    info.age= 18;	
}

//逃逸分析后,代码会被优化(标量替换)为:
public void foo() {
    String name= "queen";
    int age= 18;
}

三、AOT

优点: Java虚拟机加载已经预编译成二进制库,可以直接执行。不必等待及时编译器的预热,减少Java应用给人带来“第一次运行慢” 的不良体验。在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗。可以在程序运行初期就达到最高性能,程序启动速度快。运行产物只有机器码,打包体积小。

缺点: 由于是静态提前编译,不能根据硬件情况或程序运行情况择优选择机器指令序列,理论峰值性能不如JIT,没有动态能力,同一份产物不能跨平台运行。第一种即时编译JIT是默认模式,Java Hotspot虚拟机使用它在运行时将字节码转换为机器码。后者提前编译AOT由新颖的GraalVM编译器支持,并允许在构建时将字节码直接静态编译为机器码。

现在正处于云原生,降本增效的时代,Java相比于GoRust等其他编程语言非常大的弊端就是启动编译和启动进程非常慢,这对于根据实时计算资源,弹性扩缩容的云原生技术相冲突,Spring6借助AOT技术在运行时内存占用低,启动速度快,逐渐的来满足Java在云原生时代的需求,对于大规模使用Java应用的商业公司可以考虑尽早调研使用JDK17,通过云原生技术为公司实现降本增效。

Graalvm

Spring6支持的AOT技术,底层通过GraalVM支持,Spring也对GraalVM本机映像提供了一流的支持。GraalVM是一种高性能JDK,旨在加速用Java和其他JVM语言编写的应用程序的执行,同时还为JavaScriptPython和许多其他流行语言提供运行时。GraalVM提供两种运行 Java应用程序的方法:在HotSpot JVM上使用Graal即时JIT编译器或作为提前AOT编译的本机可执行文件。GraalVM的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外部调用成本。GraalVMHotSpot Java虚拟机添加了一个用Java编写的高级即时JIT优化编译器。

GraalVM具有以下特性:
【1】一种高级优化编译器,它生成更快、更精简的代码,需要更少的计算资源;
【2】AOT本机图像编译提前将Java应用程序编译为本机二进制文件,立即启动,无需预热即可实现最高性能;
【3】Polyglot编程在单个应用程序中利用流行语言的最佳功能和库,无需额外开销;
【4】高级工具在Java和多种语言中调试、监视、分析和优化资源消耗;

Native Image

目前业界除了这种在JVM中进行AOT的方案,还有另外一种实现Java AOT的思路,那就是直接摒弃JVM,和C/C++一样通过编译器直接将代码编译成机器代码,然后运行。这无疑是一种直接颠覆Java语言设计的思路,那就是GraalVM Native Image。它通过C语言实现了一个超微缩的运行时组件Substrate VM,基本实现了JVM的各种特性,但足够轻量、可以被轻松内嵌,这就让Java语言和工程摆脱JVM的限制,能够真正意义上实现和C/C++一样的AOT编译。这一方案在经过长时间的优化和积累后,已经拥有非常不错的效果,基本上成为Oracle官方首推的Java AOT解决方案。
Native Image是一项创新技术,可将Java代码编译成独立的本机可执行文件或本机共享库。在构建本机可执行文件期间处理的Java字节码包括所有应用程序类、依赖项、第三方依赖库和任何所需的JDK类。生成的自包含本机可执行文件特定于不需要JVM的每个单独的操作系统和机器体系结构。

Native Image 构建过程

下载GraalVM 并配置环境变量:将JAVA_HOME修改为graalvm的位置,并将Path修改为graalvmbin位置。

变量名:JAVA_HOME
变量值:D:\graalvm-ce-java17-22.3.0

检查是否安装成功

C:/Users/Administrator>java -version
openjdk version "17.0.5" 2023-10-32
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)

通过gu install native-image安装native-image插件,并通过gu list检查版本

C:/Users/Administrator>gu install native-image
......
C:/Users/Administrator>gu list
......
native-image  22.3.0  Native Image   Early adopter

对比: 通过javac xx.javanative-image xx构建文件,其中包含了SVMJDK各种库后的大小,虽然相比C/C++的二进制文件来说体积偏大,但是对比完整JVM来说,可以说是已经是非常小了。

相比于使用JVM运行,Native Image的速度要快上不少,cpu占用也更低一些,从官方提供的各类实验数据也可以看出Native Image对于启动速度和内存占用带来的提升是非常显著的:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1118306.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CVE-2019-1388 UAC提权实战

1.查看用户权限:guest来宾权限 2.右键-以管理员身份运行: 3.这个时候会弹出UAC,不用管它,点击:显示详细信息 4.然后点击蓝色字体:显示有关此发布者的证书信息 5.来到证书信息这里,点击颁发着…

线程是如何在 6 种状态之间转换的?

Java全能学习面试指南:https://javaxiaobear.cn 今天我们主要学习线程是如何在 6 种状态之间转换的。 线程的 6 种状态 就像生物从出生到长大、最终死亡的过程一样,线程也有自己的生命周期,在 Java 中线程的生命周期中一共有 6 种状态。 …

揭开 Amazon Bedrock 的神秘面纱 | 基础篇

在 2023 年 4 月,亚马逊云科技曾宣布将 Amazon Bedrock 纳入使用生成式人工智能进行构建的新工具集。Amazon Bedrock 是一项完全托管的服务,提供各种来自领先 AI 公司(包括 AI21 Labs、Anthropic、Cohere、Stability AI 和 Amazon 等&#xf…

Python学习第一天-安装Python

文章目录 前言一、下载Python二、执行安装程序三、命令行验证总结 前言 以下榜单来自于TIOBE编程语言流行指数 不多说了,Python天下第一 一、下载Python 从官网下载Python安装程序 二、执行安装程序 找到python-3.12.0-amd64.exe执行,选择Install …

基于Python3的Scapy构造DNS报文

一:DNS协议 DNS(Domain Name System)协议是计算机网络中的一种基础协议,它用于将域名(如www.baidu.com)转换为IP地址(如192.168.0.1),从而实现计算机之间的通信。 DNS 分…

【Unity地编细节】为什么Unity笔刷在地形上面刷不出来

👨‍💻个人主页:元宇宙-秩沅 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 秩沅 原创 😶‍🌫️收录于专栏:unity细节和bug 😶‍🌫️优质专栏 ⭐【…

【编解码】解码字符串中的 UNICODE 字符

前言 由于前后端交互中编码的问题,出现了这样的一串字符: {"share_names":["\u4e2d\u6587\u8def\u5f84"]}出现了unicode编码作为字符串内容的情况,直接用json解析的话会报错,所以在json解析前需要先进行转码…

Linux | 深入浅出冯诺依曼

前言 但凡是科班出生的小伙伴多多稍稍应该都听过冯诺依曼体系吧,这似乎已成为入门计算机的必备知识了,本章就带着大家一起去理解冯诺依曼体系; 一、体系构成 冯诺依曼体系主张计算机由五大部件组成,如下所示; 输入设备…

进程(0)——计算机的中的软硬件【Linux】

进程(0)——计算机的中的软硬件【Linux】 一.硬件:1.1 冯诺依曼结构:1.2 存储金字塔1.2.1输入设备和存储器:1.2.2输出设备和存储器: 二.软件:2.1 操作系统2.1.1 如何理解管理: 博主自…

【Asp.net】Asp.net core中IIS配置注意事项

1、应用地址池设为无托管代码 一、提示:关于IIS上运行ASP.NET Core 站点的“HTTP 500.19”错误 安装dotnet-hosting-3.1.2-win.exe ASP.NET Core 3.1 Runtime (v3.1.2)下载地址: https://download.visualstudio.microsoft.com/download/pr/dd119832-dc4…

visual studio Qt 开发环境中手动添加 Q_OBJECT 导致编译时出错的问题

问题简述 创建项目的时候,已经添加了类文件,前期认为不需要信号槽,就没有添加宏Q_OBJECT,后面项目需要,又加入了宏Q_OBJECT,但是发现只是添加了一个宏Q_OBJECT,除此之外没有改动其它的代码,原本…

AWS Lambda 操作 RDS 示例

实现目标 创建一个 Lambda 接收调用时传入的数据, 写入 RDS 数据库 Post 表存储文章信息. 表结构如下: idtitlecontentcreate_date1我是标题我是正文内容2023-10-21 15:20:00 AWS 资源准备 RDS 控制台创建 MySQL 实例, 不允许 Public access (后面 Lambda 需要通过 VPC 访问…

【Qt进阶之自定义控件】使用QListWidget实现自定义Item效果

目的 Q:如何在Qt库的基础上,实现自定义控件呢? A:根据官方文档回答,就是继承需实现的控件,然后实现自定义功能。 以下是实现QListWidget控件的自定义item。 先看下最终效果是如何: listItem 主…

基于YOLO实现的口罩佩戴检测 - python opemcv 深度学习 计算机竞赛

文章目录 0 前言1 课题介绍2 算法原理2.1 算法简介2.2 网络架构 3 关键代码4 数据集4.1 安装4.2 打开4.3 选择yolo标注格式4.4 打标签4.5 保存 5 训练6 实现效果6.1 pyqt实现简单GUI6.3 视频识别效果6.4 摄像头实时识别 7 最后 0 前言 🔥 优质竞赛项目系列&#xf…

正则表达式,日期选择器时间限制,报错原因

目录 一、正则表达式 1、表达式含义 2、书写表达式 二、时间限制 1、原始日期选择器改造 2、禁止选择未来时间 3、从...到...两个日期选择器的时间限制 三、Uncaught (in promise) Error报错 一、正则表达式 1、表达式含义 (1)/^([a-zA-Z0-9_.…

2.1.1BFS中的Flood Fill和最短路模型

1.池塘计数 农夫约翰有一片 N ∗ M N∗M N∗M 的矩形土地。 最近,由于降雨的原因,部分土地被水淹没了。 现在用一个字符矩阵来表示他的土地。 每个单元格内,如果包含雨水,则用”W”表示,如果不含雨水,…

Unity 文字显示动画(2)

针对第一版的优化,自动适配文字大小,TextMeshPro可以拓展各种语言。第一版字母类语言效果更好。 using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.UI;public partial class TextBeat…

CSS 三栏布局

左右两列定宽&#xff0c;中间自适应 浮动margin <style>.container {width: 500px;height: 200px;line-height: 200px;}.left {width: 100px;height: 100%;float: left;background: orange;text-align: center;}.right {width: 100px;height: 100%;float: right;back…

大模型必备算力:CPUGPU天梯图(2023年最新版)

在当今计算机世界&#xff0c;CPU、GPU和显卡的性能成为了衡量计算机性能的重要指标。今天深入了解CPU、GPU和显卡天梯图。 首先&#xff0c;CPU作为计算机的大脑&#xff0c;负责处理各种任务。它的性能主要由核心数、主频和缓存大小决定。其中&#xff0c;核心数和主频决定了…

学会使用Pandas进行数据清洗

大家好&#xff0c;如果你对数据科学感兴趣&#xff0c;那么数据清洗可能对你来说是一个熟悉的术语&#xff0c;本文将向你介绍使用Pandas进行数据清洗的过程。我们的数据通常来自多个资源&#xff0c;而且并不干净&#xff0c;它可能包含缺失值、重复值、错误或不需要的格式等…