Java 7:如何编写非常快速的Java代码

当我第一次写此博客时,我的目的是向您介绍ThreadLocalRandom类,它是Java 7中新增的用于生成随机数的类。 我已在一系列微基准测试中分析了ThreadLocalRandom的性能,以了解其在单线程环境中的性能。

结果相对令人惊讶:尽管代码非常相似,但ThreadLocalRandom速度是Math.random()两倍! 结果引起了我的兴趣,我决定对此进行进一步的研究。 我已经记录了我的分析过程。 它是对分析步骤,技术和一些JVM诊断工具的介绍,以了解小型代码段的性能差异。 所描述的工具集和技术的一些经验将使您能够为特定的Hotspot目标环境编写更快的Java代码。

好,那就足够了,让我们开始吧! 我的机器是运行Windows XP的普通Intel 386 32位双核。

Math.random()处理Random的静态单例实例,而ThreadLocalRandom -> current() -> nextDouble()处理ThreadLocalRandom的线程本地实例,该实例是Random的子类。 ThreadLocal在每次调用current()方法时引入了变量查找的开销。 考虑到我刚才说的话,在单个线程中它的运行速度是Math.random()的两倍,这确实有点令人惊讶吗? 我没想到会有如此大的差异。

同样,我使用的是Heinz博客之一中介绍的微型基准测试框架。 Heinz开发的框架解决了在现代JVM上对Java程序进行基准测试时遇到的一些挑战。 这些挑战包括:热身,垃圾回收,Javas time API的准确性,测试准确性的验证等等。

这是我可运行的基准测试类:

public class ThreadLocalRandomGenerator implements BenchmarkRunnable {private double r;@Overridepublic void run() {r = r + ThreadLocalRandom.current().nextDouble();}public double getR() {return r;}@Overridepublic Object getResult() {return r;}}public class MathRandomGenerator implements BenchmarkRunnable {private double r;@Overridepublic void run() {r = r + Math.random();}public double getR() {return r;}@Overridepublic Object getResult() {return r;}
}

让我们使用Heinz的框架运行基准测试:

public class FirstBenchmark {private static List<BenchmarkRunnable> benchmarkTargets = Arrays.asList(new MathRandomGenerator(),new ThreadLocalRandomGenerator());public static void main(String[] args) {DecimalFormat df = new DecimalFormat("#.##");for (BenchmarkRunnable runnable : benchmarkTargets) {Average average = new PerformanceHarness().calculatePerf(new PerformanceChecker(1000, runnable), 5);System.out.println("Benchmark target: " + runnable.getClass().getSimpleName());System.out.println("Mean execution count: " + df.format(average.mean()));System.out.println("Standard deviation: " + df.format(average.stddev()));System.out.println("To avoid dead code coptimization: " + runnable.getResult());}}
}

注意:为了确保JVM不会将代码标识为“死代码”,我返回了一个字段变量,并立即打印出基准测试的结果。 这就是为什么我的可运行类实现名为RunnableBenchmark的接口。 我已经运行了三次基准测试。 第一次运行是在默认模式下,启用了内联和JIT优化:

Benchmark target: MathRandomGenerator
Mean execution count: 14773594,4
Standard deviation: 180484,9
To avoid dead code coptimization: 6.4005410634212025E7
Benchmark target: ThreadLocalRandomGenerator
Mean execution count: 29861911,6
Standard deviation: 723934,46
To avoid dead code coptimization: 1.0155096190946539E8

然后再次不进行JIT优化(VM选项-Xint ):

Benchmark target: MathRandomGenerator
Mean execution count: 963226,2
Standard deviation: 5009,28
To avoid dead code coptimization: 3296912.509302683
Benchmark target: ThreadLocalRandomGenerator
Mean execution count: 1093147,4
Standard deviation: 491,15
To avoid dead code coptimization: 3811259.7334526842

最后一个测试是使用JIT优化,但是使用-XX:MaxInlineSize=0 ,它(几乎)禁用了内联:

Benchmark target: MathRandomGenerator
Mean execution count: 13789245
Standard deviation: 200390,59
To avoid dead code coptimization: 4.802723374491231E7
Benchmark target: ThreadLocalRandomGenerator
Mean execution count: 24009159,8
Standard deviation: 149222,7
To avoid dead code coptimization: 8.378231170741305E7

让我们仔细地解释结果:借助完整的JVM JIT优化, ThreadLocalRanom速度是Math.random()两倍。 关闭JIT优化表明,两者的性能相同(差)。 方法内联似乎使性能相差30%。 其他差异可能归因于其他优化技术 。

JIT编译器可以更有效地调整ThreadLocalRandom原因之一是ThreadLocalRandom.next()的改进实现。

public class Random implements java.io.Serializable {
...protected int next(int bits) {long oldseed, nextseed;AtomicLong seed = this.seed;do {oldseed = seed.get();nextseed = (oldseed * multiplier + addend) & mask;} while (!seed.compareAndSet(oldseed, nextseed));return (int)(nextseed >>> (48 - bits));}
...
}public class ThreadLocalRandom extends Random {
...protected int next(int bits) {rnd = (rnd * multiplier + addend) & mask;return (int) (rnd >>> (48-bits));}
...
}

第一个片段显示Random.next() ,它在Math.random()的基准测试中大量使用。 与ThreadLocalRandom.next()相比,该方法需要更多的指令,尽管这两种方法都做同样的事情。 在Random类中, seed变量将全局共享状态存储到所有线程,并且每次调用next()方法时都会更改。 因此,需要AtomicLong安全地访问和更改对nextDouble()调用中的seed值。 另一方面, ThreadLocalRandom是–很好–线程局部:-) next()方法不必是线程安全的,可以使用普通的long变量作为种子值。

关于方法内联和ThreadLocalRandom

方法内联是一种非常有效的JIT优化。 在频繁执行的热路径中,热点编译器决定将被调用方法(子方法)的代码内联到调用方方法(父方法)中。 内联具有重要的好处。 它显着降低了方法调用的动态频率,从而节省了执行这些方法调用所需的时间。 但更重要的是,内联会产生更大的代码块,以供优化程序使用。 这就造成了一种情况,大大提高了传统编译器优化的效率,克服了提高Java编程语言性能的主要障碍。”

从Java 7开始,您可以使用诊断JVM选项监视方法内联。 使用' -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining '运行代码将显示JIT编译器的内联工作。 以下是Math.random()基准测试输出的相关部分:

@ 13   java.util.Random::nextDouble (24 bytes)@ 3   java.util.Random::next (47 bytes)   callee is too large@ 13   java.util.Random::next (47 bytes)   callee is too large

JIT编译器无法内联Random.next()中调用的Random.nextDouble() 。 这是ThreaLocalRandom.next()的内联输出:

@ 8   java.util.Random::nextDouble (24 bytes)@ 3   java.util.concurrent.ThreadLocalRandom::next (31 bytes)@ 13   java.util.concurrent.ThreadLocalRandom::next (31 bytes)

由于next()方法较短(31个字节),因此可以内联它。 因为在两个基准测试中都强烈调用next()方法,所以该日志表明方法内联可能是ThreadLocalRandom显着提高执行速度的原因之一。

为了验证这一点并查找更多信息,需要深入研究汇编代码。 使用Java 7 JDK,可以将汇编代码打印到控制台中。 有关如何启用-XX:+PrintAssembly VM选项的信息,请参见此处 。 该选项将打印出JIT优化的代码,这意味着您可以看到JVM实际执行的代码。 我已经将相关的汇编代码复制到下面的链接中。

此处的ThreadLocalRandomGenerator.run()的汇编代码。
MathRandomGenerator.run()的汇编代码在此处 。
Math.random() 在此处调用的Random.next()的汇编代码。

汇编代码是机器特定的低级代码,比字节代码要复杂得多。 让我们尝试在我的基准测试中验证方法内联对性能的影响,以及:JIT编译器如何处理ThreadLocalRandomMath.random ()还有其他明显的区别吗? 在ThreadLocalRandomGenerator.run() ,没有对任何子例程(如Random.nextDouble()ThreatLocalRandom.next()过程调用。 仅可见一个虚拟(因此很昂贵)的ThreadLocal.get() )方法调用(请参阅ThreadLocalRandomGenerator.run()程序集的第35行)。 其他所有代码都内联到ThreadLocalRandomGenerator.run() 。 在的情况下MathRandomGenerator.run()两个虚拟方法调用到Random.next()见块B4线204页及以后中的汇编代码MathRandomGenerator.run() 这一事实证实了我们的怀疑,即方法内联是导致性能差异的一个重要根本原因。 此外,由于同步的麻烦, Random.next()需要的汇编指令要多得多(并且有些昂贵!),这在执行速度方面也适得其反。

了解invokevirtual指令的开销

那么,为什么(虚拟)方法调用昂贵且方法内联如此有效? invokevirtual指令的指针不是类实例中具体方法的偏移量。 编译器不知道类实例的内部布局。 相反,它生成对实例方法的符号引用,这些符号引用存储在运行时常量池中。 这些运行时常量池项将在运行时解析以确定实际的方法位置。 这种动态(运行时)绑定需要验证,准备和解决,​​这可能会大大影响性能。 (有关详细信息,请参见JVM规范中的调用方法和链接 )。

目前为止就这样了。 免责声明:当然,解决性能难题需要了解的主题列表无穷无尽。 除了微基准测试,JIT优化,方法内联,java字节码,assemby语言等等之外,还有更多的知识要理解。 同样,除了虚拟方法调用或昂贵的线程同步指令之外,还有更多导致性能差异的根本原因。 但是,我认为我所介绍的主题是此类深入研究的一个好的开始。 期待批评和愉快的评论!

参考资料:来自JCG合作伙伴 Niklas的“ Java 7:如何编写真正快速的Java代码”。


翻译自: https://www.javacodegeeks.com/2012/01/java-7-how-to-write-really-fast-java.html

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

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

相关文章

[地图开发][算法及数据结构]四叉树原理

参考&#xff1a;http://blog.csdn.net/zhouxuguang236/article/details/12312099 原博客地址还有c&#xff0b;&#xff0b;源码。。。 四叉树索引的基本思想是将地理空间递归划分为不同层次的树结构。它将已知范围的空间等分成四个相等的子空间&#xff0c;如此递归下去&…

按键 粘贴上一个命令_合并单元格、选择性粘贴的快捷键都是啥?今天一次告诉你……...

经常有人在群里问&#xff0c;合并单元格的快捷键是什么&#xff1f;选择性粘贴数值的快捷键是什么&#xff1f;今天就来聊聊快捷键的一些冷门知识……Alt键的作用快捷键其实就是一些组合键&#xff0c;主要用到Ctrl、shift、Alt这三个键其中之一或者是几个&#xff0c;再加上其…

Spring MVC和JQuery用于Ajax表单验证

在本教程中&#xff0c;我们将看到如何使用Ajax和Spring MVC和JQuery在服务器端验证表单。 Spring MVC为通过注释驱动的配置采用Ajax提供了非常方便的过程。 我们将使用此注释驱动的配置以JSON数据的形式发送Ajax响应。 响应将包含表单验证的状态&#xff0c;并且表单数据中存在…

Linux学习笔记——gzip命令

这个 gzip 程序被用来压缩一个或多个文件。当执行 gzip 命令时&#xff0c;则原始文件的压缩版会替代原始文件。 相对应的 gunzip 程序被用来把压缩文件复原为没有被压缩的版本。gzip 选项&#xff1a;选项 说明-c把输出写入到标准输出&#xff0c;并且保留原始文件。也有可能用…

java集合类——Stack类

查看java的API文档&#xff0c;Stack继承Vector类。 栈的特点是后进先出。 API中Stack自身的方法不多&#xff0c;基本跟栈的特点有关。 Java代码 import java.util.Stack; public class StackTest { public static void main(String[] args) { Stack&l…

免装版_无缝贴图制作软件 PixPlant2中文免装版

点击上方蓝字关注我们如您喜欢我们的公众号&#xff0c;不妨推荐给身边的朋友资源介绍&#xff1a;资源来源于网络&#xff0c;很多时候我们从网上找的贴图并不是无缝的&#xff0c;而且一般都没有高光/法线贴图这些&#xff0c;在材质的模拟上就要差了很多&#xff0c;在这里小…

Java中使用Map and Fold进行功能性编程

在函数式编程中&#xff0c;Map和Fold是两个非常有用的运算符&#xff0c;它们属于每种函数式语言。 如果Map和Fold运算符是如此强大且必不可少&#xff0c;那么您如何解释说即使Java编程语言缺少这两个运算符&#xff0c;我们也可以使用Java来完成工作&#xff1f; 事实是&…

Mysql 分页语句Limit用法

Mysql 分页语句Limit用法 1、Mysql的limit用法 在我们使用查询语句的时候&#xff0c;经常要返回前几条或者中间某几行数据&#xff0c;这个时候怎么办呢&#xff1f;不用担心&#xff0c;mysql已经为我们提供了这样一个功能。 Sql代码 SELECT * FROM table LIMIT [offset,] r…

波纹扩散_C4D_动画amp;RS波纹扩散效果J_014

C4D-效果扩散效果&#xff0c;Redshift混合冰材质&#xff1b;利用顶点贴图扩散效果制作&#xff0c;RS混合调用顶点贴图。视频教程时长22分钟。对象为可编辑对象才能用顶点贴图。冰结域的扩展是这个动画的重点&#xff0c;在这个模式下&#xff0c;权重会根据半径向外扩展&…

软件测试工程师简历项目经验怎么写?--9999个已成功入职的软件测试工程师真实简历

简历是我们求职的第一步&#xff0c;也是非常重要的一步。 青云叔叔看过太多简历&#xff0c;最快3秒就淘汰一份简历&#xff0c;因为其实我们每天要收到很多简历进行筛选&#xff0c;那么面试官其实也是会很快进行对简历进行判断的&#xff0c;如果你对简历写的一塌糊涂&…

【poj2464】树状数组

这道题。。太特么多细节了。。 题意&#xff1a;在平面直角坐标系中给你N个点&#xff0c;stan和ollie玩一个游戏&#xff0c;首先stan在竖直方向上画一条直线&#xff0c;该直线必须要过其中的某个点&#xff0c;然后ollie在水平方向上画一条直线&#xff0c;该直线的要求是要…

azure git怎么使用_Azure(一)Azure Traffic Manager为我们的Web项目提供负载均衡

一&#xff0c;引言上一篇讲到我们将自己的Net Core Web 项目部署到 Azure 的 Web App 的一项 pass 服务&#xff0c;假如随着项目的日益增长的访问量&#xff0c;之前部署到单节点的应用可能无法保证其稳定性&#xff0c;可能会导致系统宕机等等问题&#xff0c;这个时候&…

文字描边_如何在网页里实现文字描边效果

文字描边想要在网页里实现文本描边效果&#xff0c;在以前只能使用Photoshop等来实现&#xff0c;但现在只需要一个text-stroke属性&#xff0c;即可轻松做到文本描边&#xff0c;渐变文本描边&#xff0c;甚至图片文本描边。01语法text-stroke: text-stroke是一个复合属性&…

javascript数据结构-栈

github博客地址 栈&#xff08;stack&#xff09;又名堆栈&#xff0c;它是一种运算受限的线性表。遵循后进先出原则&#xff0c;像垃圾桶似的。功能实现依然按照增删改查来进行&#xff0c;内部数据存储可以借用语言原生支持的数组。 栈类 function Stack(){this.data []; }添…

”二柱子“个人项目

”二柱子“个人项目 关于二柱子的个人项目&#xff0c;据说……是这么发生的…… 二柱子因为懒(,,• ₃ •,,)&#xff0c;要给他上小学的儿子编写个能够出小学四则运算题目的程序。老师上课的时候又添加了条件&#xff1a; 1、打印至少30道题 2、除了整数之外&#xff0c;还要…

Eclipse中的集成Git插件删除线上远程分支

Eclipse 的忠实党,在使用Git 多人协作以分支的形式开发应用时分支合并到主干后往往再没什么用(我的做法是保留一两周再干掉),在此记录使用Eclipse的Git 插件来删除无用的分支。 操作步骤: 项目右键 — Team — Remote — Push — Next — Finesh 1,下拉框选择你要删除的远程分支…

从MongoDB GridFS流式传输文件

不久前&#xff0c;我在Twitter上发布了自己的最新作品&#xff0c;即从MongoDB GridFS传输文件进行下载&#xff08;而不是将整个文件存储到内存中然后提供服务&#xff09;&#xff0c;这是我取得的一个小胜利。 我答应就此事写博客&#xff0c;但不幸的是&#xff0c;我的特…

Css Sprites 多张图片整合在一张图片上

CSS Sprites原理&#xff1a; CSS Sprites其实就是把网页中一些背景图片整合到一张图片文件中&#xff0c;再利用CSS的“background-image”&#xff0c;“background- repeat”&#xff0c;“background-position”的组合进行背景定位&#xff0c;background-position可以用数…

无需复杂插件即可从Eclipse启动和调试Tomcat

像Eclipse这样的现代IDE提供了各种插件来简化Web开发。 但是&#xff0c;我相信将Tomcat作为“常规” Java应用程序启动仍然可以提供最佳的调试体验。 大多数情况下&#xff0c;这是因为这些工具将Tomcat或任何其他servlet容器作为外部进程启动&#xff0c;然后在其上附加一个远…

flutter 国际化_Flutter 开发实战资源推荐

开工第一天&#xff0c;来点轻松的资源推荐。这是一篇实战类资源推荐&#xff0c;其实Flutter的入门资料官方已经做得很好了&#xff0c;如果你是零基础&#xff0c;还是建议先啃一遍官方的教程&#xff0c;然后再看以下实战资源&#xff0c;相信在你看官方课程中涉及到的一些疑…