类固醇上的Java:5种超级有用的JIT优化技术

Java开发人员? 优化您的生产监控。 请在所有已记录的错误,警告和异常之后查看源代码,调用堆栈和变量状态- 尝试Takipi 。

最有用的JVM JIT优化有哪些?如何使用它们?

即使您没有积极计划,JVM也有很多技巧可以帮助您的代码更好地执行。 有些人实际上不需要您提供任何帮助,其他人可以在此过程中使用一些帮助。

在这篇文章中,我们与Takipi的研发团队负责人Moshe Tsur进行了聊天,并有机会分享了他有关JVM的即时(JIT)编译器的一些技巧。

让我们看看幕后发生了什么。

编写一次,随处运行,及时优化

大多数人都知道,Javac编译器将其Java源代码转换为字节码,然后由JVM运行,然后JVM将其编译为Assembly,并将其提供给CPU。

很少有人知道兔子的洞越来越深。 JVM有2种不同的操作模式。

生成的字节码是原始Java源代码的准确表示,没有进行任何优化。 当JVM将其转换为Assembly时,事情变得更加棘手,魔术开始了:

  1. 解释模式 – JVM照原样读取和运行字节码。
  2. 编译模式(字节码到汇编) – JVM放松了控制,并且不涉及字节码。

两者之间的链接是JIT编译器。 您可能已经猜到,解释模式比没有中间人直接在CPU上运行要慢得多。

解释模式为Java平台提供了宝贵的机会来收集有关代码及其在实践中的实际行为的信息-使其有机会学习如何优化所生成的Assembly代码。

经过足够的运行时间后,JIT编译器便能够在程序集级别执行优化。 主要是,根据代码的实际行为,最大程度地减少不必要的跳转,从而增加应用程序的大量开销。 此外,该过程不会在应用程序的“预热阶段”后停止,并且可能会多次发生以进一步优化组装。

在Takipi ,我们正在构建一个Java代理来监视生产中的服务器,我们非常重视开销。 每一点代码都经过优化,在此过程中,我们有机会使用和学习了一些很酷的JIT功能。

以下是5个更有用的示例:

1.空检查消除

在许多情况下,在开发中将某些条件检查添加到代码中非常有意义。 就像臭名昭著的null检查一样。 空值得到特殊处理也就不足为奇了,因为这是生产中发生最高例外的原因 。

但是,在大多数情况下,可以从优化的汇编代码中消除显式的空检查,并且这种情况适用于很少发生的任何情况。 在这种情况确实发生的极少数情况下,JIT使用一种称为“罕见陷阱”的机制,让它知道需要回退并通过撤回在执行时需要执行的指令集来修复优化。发生–无需显示实际的NullPointerException。

这些检查之所以成为优化代码的原因,是因为它们可能会在汇编代码中引入跳转,从而减慢其执行速度。

让我们看一个简单的例子:

private static void runSomeAlgorithm(Graph graph) {if (graph == null) {return;}// do something with graph
}

如果JIT看到从未使用null调用该图,则编译后的版本看起来就像它反映的代码未经null检查:

private static void runSomeAlgorithm(Graph graph) {// do something with graph
}

底线:我们不需要做任何特殊的事情就能享受到这种优化,并且当出现罕见情况时,JVM会知道“反优化”并获得理想的结果。

2.分支预测

与Null Check Elimination(空检查消除)相似,还有另一种JIT优化技术,可帮助它确定某些代码行是否比其他代码“ 更热 ”并且发生频率更高。

我们已经知道,如果某种条件很少或永远不会成立,那么“罕见陷阱”机制很可能会介入并将其从已编译的大会中消除。

如果存在不同的平衡,则IF条件的两个分支都成立,但是一个分支的发生比另一个分支多,JIT编译器可以根据最常见的一个对它们进行重新排序,并显着减少Assembly跳转的次数。

这是实际的工作方式:

private static int isOpt(int x, int y) {int veryHardCalculation = 0;if (x >= y) {veryHardCalculation = x * 1000 + y;} else {veryHardCalculation = y * 1000 + x;}return veryHardCalculation;
}

现在,假设大部分时间x <y,该条件将被翻转以统计地减少Assembly跳转的次数:

private static int isOpt(int x, int y) {int veryHardCalculation = 0;if (x < y) {// this would not require a jumpveryHardCalculation = y * 1000 + x;return veryHardCalculation;} else {veryHardCalculation = x * 1000 + y;return veryHardCalculation;}
}

如果您不确定,我们实际上已经对其进行了一些测试,并提取了经过优化的Assembly代码:

0x00007fd715062d0c: cmp    %edx,%esi
0x00007fd715062d0e: jge    0x00007fd715062d24  ;*if_icmplt; - Opt::isOpt@4 (line 117)

如您所见,跳转指令是jge(如果大于或等于,则跳转),与条件的else分支相对应。

底线:我们享受的另一种即用型JIT优化。 在最近的文章中,我们还讨论了分支预测,其中涉及一些最有趣的Stackoverflow Java答案 。

在此处显示的示例中,我们看到了如何使用分支预测来解释为什么在有助于分支预测的某些条件下在排序数组上运行操作要快得多的原因。

3.循环展开

您可能已经注意到,JIT编译器不断尝试消除已编译代码中的Assembly跳转。

这就是为什么循环闻起来像麻烦。

每次迭代实际上都是一个Assembly跳转回指令集的开始。 通过循环展开,JIT编译器将打开循环,并仅一个接一个地重复相应的Assembly指令。

从理论上讲,当将Java转换为字节码时,javac可以做类似的事情,但是JIT编译器对代码将在其上运行的实际CPU有了更好的了解,并且知道如何以更加有效的方式微调代码。

例如,让我们看一下将矩阵乘以向量的方法:

private static double[] loopUnrolling(double[][] matrix1, double[] vector1) {double[] result = new double[vector1.length];for (int i = 0; i < matrix1.length; i++) {for (int j = 0; j < vector1.length; j++) {result[i] += matrix1[i][j] * vector1[j];}}return result;
}

展开的版本如下所示:

private static double[] loopUnrolling2(double[][] matrix1, double[] vector1) {double[] result = new double[vector1.length];for (int i = 0; i < matrix1.length; i++) {result[i] += matrix1[i][0] * vector1[0];result[i] += matrix1[i][1] * vector1[1];result[i] += matrix1[i][2] * vector1[2];// and maybe it will expand even further - e.g. 4 iterations, thus// adding code to fix the indexing// which we would waste more time doing correctly and efficiently}return result;
}

一次又一次地重复相同的操作,而不会产生跳转开销:

....
0x00007fd715060743: vmovsd 0x10(%r8,%rcx,8),%xmm0  ;*daload; - Opt::loopUnrolling@26 (line 179)
0x00007fd71506074a: vmovsd 0x10(%rbp),%xmm1        ;*daload; - Opt::loopUnrolling@36 (line 179)
0x00007fd71506074f: vmulsd 0x10(%r12,%r9,8),%xmm1,%xmm1
0x00007fd715060756: vaddsd %xmm0,%xmm1,%xmm0       ;*dadd; - Opt::loopUnrolling@38 (line 179)
0x00007fd71506075a: vmovsd %xmm0,0x10(%r8,%rcx,8)  ;*dastore; - Opt::loopUnrolling@39 (line 179)
....

底线: JIT编译器在展开循环方面做得很好,只要您使它们的内容简单而没有任何不必要的复杂性即可。

同时也不建议您尝试优化此过程并自行展开,结果可能会导致更多问题,甚至无法解决。

4.内联方法

接下来,进行另一个跳跃杀手优化。 实际上,方法调用是汇编跳转的重要来源。 在可能的情况下,JIT编译器将尝试内联它们,并消除往返的跳转,发送参数和返回值的需要-将其全部内容传递给调用方法。

还可以通过2个JVM参数来微调JIT编译器内联方法的方式:

  1. -XX:MaxInlineSize –可以内联的方法的字节码的最大大小(即使不经常执行,对于任何方法也都可以这样做)。 默认值约为35个字节。
  2. -XX:FreqInlineSize-应该内联的被视为热点(频繁执行)的方法的字节码的最大大小。 默认值因平台而异。

例如,让我们看一下一种计算简单线的坐标的方法:

private static void calcLine(int a, int b, int from, int to) {Line l = new Line(a, b);for (int x = from; x <= to; x++) {int y = l.getY(x);System.err.println("(" + x + ", " + y + ")");}
}static class Line {public final int a;public final int b;public Line(int a, int b) {this.a = a;this.b = b;}// Inliningpublic int getY(int x) {return (a * x + b);}
}

优化的内联版本将消除跳转,参数l和x的发送以及y的返回:

private static void calcLine(int a, int b, int from, int to) {Line l = new Line(a, b);for (int x = from; x <= to; x++) {int y = (l.a * x + l.b);System.err.println("(" + x + ", " + y + ")");}
}

底线:内联是一种超级有用的优化,但是只有在您使用最少的行数使方法尽可能简单的情况下,内联才会启动。 复杂的方法不太可能内联,因此这是您可以帮助JIT编译器完成其工作的一点。

5.线程字段和线程本地存储(TLS)

事实证明,线程字段比常规变量快得多。 线程对象存储在实际的CPU寄存器中,从而使其字段成为非常有效的存储空间。

使用线程本地存储,您可以创建存储在Thread对象上的变量。

以下是请求计数器的简单示例,说明了如何访问线程本地存储:

private static void handleRequest() {if (counter.get() == null) {counter.set(0);}counter.set(counter.get() + 1);

在相应的汇编代码中,我们可以看到数据直接放置在寄存器中,与静态类变量相比,可以更快地访问数据:

0x00007fd71508b1ec: mov    0x1b0(%r15),%r10   ;*invokestatic currentThread; - java.lang.ThreadLocal::get@0 (line 143); - Opt::handleRequest@3 (line 70)0x00007fd71508b1f3: mov    0x50(%r10),%r11d   ;*getfield threadLocals; - java.lang.ThreadLocal::getMap@1 (line 213); - java.lang.ThreadLocal::get@6 (line 144); - Opt::handleRequest@3 (line 70)

底线:某些敏感数据类型可能更好地存储在“线程本地存储”中,以便更快地访问和检索。

最后的想法

JVMs JIT编译器是Java平台上引人入胜的机制之一。 它在不牺牲可读性的情况下优化了代码的性能。 不仅如此,除了内联的“静态”优化方法之外,它还基于代码在实践中的执行方式来做出决策。

我们希望您喜欢一些JVM最有用的优化技术,并且很高兴在下面的评论部分中听到您的想法。

Java开发人员? 优化您的生产监控。 请在所有已记录的错误,警告和异常之后查看源代码,调用堆栈和变量状态- 尝试Takipi 。

翻译自: https://www.javacodegeeks.com/2016/08/java-steroids-5-super-useful-jit-optimization-techniques.html

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

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

相关文章

poj3714 最近点对

最近点对&#xff0c;采用分治方法。过程&#xff1a; 1对原数组依据x左标从小到大排序。 2二分数组&#xff0c;左边求出最小值&#xff0c;右边求出最小值&#xff0c;我们求最小的。 3找出对于左右两边的可能小于当前最小值的最近点对&#xff0c;更新最小值。 这题目需要区…

Matlab 格式化字符串sscanf

sscanf 是按一定的格式从字符串中读取出字符,它有以下几种用法: A = sscanf(str, format) A = sscanf(str, format, sizeA) [A, count] = sscanf(...) [A, count, errmsg] = sscanf(...) [A, count, errmsg, nextindex] = sscanf(...) Description A = sscanf(str, forma…

2018牛客暑假多校二 D(贪心)

题目描述&#xff1a; 你要按照顺序以此经过n个商店&#xff0c;每到达一个商店你可以购买一件物品&#xff0c;也可以出售你手中的商品。 同一时刻你手上最多拿一件商品。在第i个商店购买和出售的代价都是a[i]。 问你经过完n个商店后的最大收益。 同时&#xff0c;在最大化收…

ZOJ1450 Minimal Circle 最小圆覆盖

包含点集所有点的最小圆的算法 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId450 平面上有n个点&#xff0c;给定n个点的坐标&#xff0c;试找一个半径最小的圆&#xff0c;将n 个点全部包围&#xff0c;点可以在圆上。 1. 在点集中任取3点A,B,C。 2. 作一个包…

遗传算法各Matlab工具箱简介

关于matlab遗传算法工具箱主要有三种&#xff1a;1、gaot工具箱&#xff1a;这是网上流传的免费的工具箱&#xff0c;网上对它介绍的资料也挺多&#xff0c;它不是Matlab软件自带的&#xff0c;但可以自己配置使用。飞思科技产品研发中心编著《Matlab 6.X辅助优化计算与设计》第…

独眼巨人反应组织了Java 8库的寒武纪爆发

什么是独眼巨人反应&#xff1f; Lambda表达式和默认方法在Java 8中的出现预示了Java语言十年来最大的结构性变化。 在此基础上构建了一些新的很酷的API&#xff0c;例如Stream, Optional, CompletableFuture最终Java开发人员可以以更实用的样式进行编码。 尽管这是非常受欢迎的…

C语言中文件定位函数总结

C语言中文件定位函数主要是&#xff1a;fseek, ftell, fsetpos, fgetpos。 先来讲前两个函数&#xff0c;这是最基本的定位函数&#xff1a; fseek函数&#xff1a;能把文件指针移动到文件任何位置&#xff0c;其原型是&#xff1a;int fseek(FILE *fp, long offset, int fromw…

API

api 百科名片 API&#xff08;Application Programming Interface,应用程序编程接口&#xff09;是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于某软件或硬件的以访问一组例程的能力&#xff0c;而又无需访问源码&#xff0c;或理解内部工作机制的细节。目…

Matlab gatool使用方法

可以通过输入gatool或者optimtool(ga)打开图形界面或者用ga()运行命令行函数设置Fitnessfunction 和对应的Number of variables,为了提高效率最好向量化适应度函数,然后设置Vectorize参数为On可以选择相应参数进行设置Population种群参数Fitness scalling适应度比例参数Selecti…

系统模块 OS

os.system("系统命令") 调用系统命令 os.system("task kill /f /im 系统的进程") 关闭系统进程 os.listdir(地址) 扫描目录里面的文件。默认的是当前文件夹 返回一个列表 import os # os.listdir(地址) 扫描目录里面的文件。默认的是当前文件夹 print(os.…

Matlab非线性拟合工具箱cftool

一、 单一变量的曲线逼近 Matlab有一个功能强大的曲线拟合工具箱 cftool &#xff0c;使用方便&#xff0c;能实现多种类型的线性、非线性曲线拟合。下面结合我使用的 Matlab R2007b 来简单介绍如何使用这个工具箱。 假设我们要拟合的函数形式是 yA*x*x B*x, 且A>0,B>0…

jsf标签p:ajax_JSF简单Ajax示例

jsf标签<p:ajax>今天&#xff0c;我们将看到一些使用JSF的Ajax简单样本。 如果要查看有关JSF / Web应用程序的其他文章&#xff0c;请单击以下链接&#xff1a; 重定向后的JSF持久化对象和消息 &#xff0c; 使用JAAS和JSF进行用户登录验证 &#xff0c; JSF&#xff1a;…

C#中数据库事务、存储过程基本用法

SQL 事务 public bool UpdateQsRegisterSql(List<string> ids, int newQueueId, string newQueueName){using (SqlConnection con new SqlConnection(DBHelper.Instance.ConStr)){SqlTransaction tran null;try{con.Open();SqlCommand cmd con.CreateCommand();tran …

第一章 初识Mathematica

第一章 初识Mathematica 1&#xff0e;Mathematica是什么 Matematica是由美国Wolfram公司研究开发的一个著名的数学软件&#xff0c;它提供了非常强大的功能&#xff0c;能够完成符号运算、数学图形的绘制、甚至动画制作等多种操作。Matematica的基本系统主要是用C语言开发的&…

matlab中统计工具箱函数名大全

MATLAB统计工具箱包括概率分布、方差分析、假设检验、分布检验、非参数检验、回归分析、判别分析、主成分分析、因子分析、系统聚类分析、K均值聚类分析、试验设计、决策树、多元方差分析、统计过程控制和统计图形绘制等。优化工具箱包括无约束最优化、有约束最优化、二次规划、…

BIOS维修技术

BIOS是电脑中最基础且最重要的程序&#xff0c;为电脑提供最低级且最直接的硬件控制&#xff0c;电脑的原始操作都是依照固化在BIOS里的程序来完成的。因此如果BIOS出现故障将会导致影响电脑的正常工作。BIOS故障有很多&#xff0c;根据常见的BIOS故障现象及其产生的原因&#…

使用Java Servlet,JSP标签和Stormpath快速构建Java Web App

建筑物身份管理&#xff0c;包括身份验证和授权&#xff1f; 尝试Stormpath&#xff01; 我们的REST API和强大的Java SDK支持可以消除您的安全风险&#xff0c;并且可以在几分钟内实现。 注册 &#xff0c;再也不会建立auth了&#xff01; 我们刚刚发布了Java SDK的主要升级版…

Matlab optimtool优化(Optimization)工具箱

1 工具箱概述1.1 功能&#xff08;1&#xff09;求解无约束条件非线性极小值&#xff1b;&#xff08;2&#xff09;求解约束条件下非线性极小值&#xff0c;包括目标逼近问题、极大-极小值问题和半无限极小值问题&#xff1b;&#xff08;3&#xff09;求解二次规划和线性规划…

Python目录链接

第1章  就这么愉快的开始吧 课时1&#xff1a;我和python的第一次亲密接触 一、Python3的下载与安装 二、从IDIE启动Python 三、尝试点新的东西 四、为什么会这样&#xff1f; 五、课时01课后习题及答案 第2章  用Python设计一个游戏 课时2&#xff1a;用python设计第一个…

谚语幸运的开始

谚语:幸运的开始 今天收到一个朋友的邮件&#xff0c;打开一看&#xff0c;是幸运谚语&#xff0c;自以为谚语写的非常好&#xff0c;所以转写出来与朋友们分享&#xff01;希望你能得到幸运哦&#xff01;荷兰的谚语幸运谚语幸运的开始关于金钱&#xff1a;你有了钱&#xff…