方法内联在JVM中有多积极?

IntelliJ IDEA中使用Ctrl + Alt + M 提取方法 。 Ctrl + Alt + M。 这就像选择一段代码并按此组合一样简单。 Eclipse也有它 。 我讨厌冗长的方法。 对于我来说,闻起来太久了:

public void processOnEndOfDay(Contract c) {if (DateUtils.addDays(c.getCreated(), 7).before(new Date())) {priorityHandling(c, OUTDATED_FEE);notifyOutdated(c);log.info("Outdated: {}", c);} else {if(sendNotifications) {notifyPending(c);}log.debug("Pending {}", c);}
}

首先,它具有不可读的条件。 无关紧要的实现方式,重要的是做什么。 因此,让我们首先提取它:

public void processOnEndOfDay(Contract c) {if (isOutdated(c)) {priorityHandling(c, OUTDATED_FEE);notifyOutdated(c);log.info("Outdated: {}", c);} else {if(sendNotifications) {notifyPending(c);}log.debug("Pending {}", c);}
}private boolean isOutdated(Contract c) {return DateUtils.addDays(c.getCreated(), 7).before(new Date());
}

显然,此方法并不真正属于这里( F6 –移动实例方法):

public void processOnEndOfDay(Contract c) {if (c.isOutdated()) {priorityHandling(c, OUTDATED_FEE);notifyOutdated(c);log.info("Outdated: {}", c);} else {if(sendNotifications) {notifyPending(c);}log.debug("Pending {}", c);}
}

注意到不同了吗? 我的IDE使isOutdated()Contract的实例方法,听起来isOutdated() 。 但是我还是不开心。 这种方法发生了太多的事情。 一个分支执行一些与业务相关的priorityHandling() ,一些系统通知和日志记录。 其他分支执行条件通知和日志记录。 首先,让我们将处理过时的合同转移到单独的方法中:

public void processOnEndOfDay(Contract c) {if (c.isOutdated()) {handleOutdated(c);} else {if(sendNotifications) {notifyPending(c);}log.debug("Pending {}", c);}
}private void handleOutdated(Contract c) {priorityHandling(c, OUTDATED_FEE);notifyOutdated(c);log.info("Outdated: {}", c);
}

也许有人说这足够了,但是我看到分支之间的惊人不对称性。 handleOutdated()是非常高级的,而发送else分支是技术性的。 软件应易于阅读,因此请勿将不同级别的抽象彼此混用。 现在我很高兴:

public void processOnEndOfDay(Contract c) {if (c.isOutdated()) {handleOutdated(c);} else {stillPending(c);}
}private void handleOutdated(Contract c) {priorityHandling(c, OUTDATED_FEE);notifyOutdated(c);log.info("Outdated: {}", c);
}private void stillPending(Contract c) {if(sendNotifications) {notifyPending(c);}log.debug("Pending {}", c);
}

这个例子有些人为,但实际上我想证明一些不同的东西。 这些天并不经常出现,但是仍然有开发人员担心提取方法会认为它在运行时速度较慢。 他们无法理解JVM是一款很棒的软件(它可能远远超过Java语言),它内置了许多真正令人惊叹的运行时优化。 首先,较短的方法更容易推理。 流动更明显,范围更短,副作用更明显。 使用长方法,JVM可能会简单地放弃。 第二个原因更为重要:

方法内联

如果JVM发现一遍又一遍执行的小方法,它将简单地用其主体替换该方法的每次调用。 以此为例:

private int add4(int x1, int x2, int x3, int x4) {return add2(x1, x2) + add2(x3, x4);
}private int add2(int x1, int x2) {return x1 + x2;
}

您可能几乎可以确定,一段时间后JVM将摆脱add2()并将代码转换为:

private int add4(int x1, int x2, int x3, int x4) {return x1 + x2 + x3 + x4;
}

重要说明是它是JVM,而不是编译器。 生成字节码时, javac非常保守,并将所有工作都留给了JVM。 这个设计决定非常出色:

  • JVM对目标环境,CPU,内存,体系结构有更多了解,并且可以更加积极地进行优化
  • JVM可以发现代码的运行时特征,例如,哪些方法最常执行,哪些虚拟方法只有一个实现等。
  • 使用旧Java编译的.class将在较新的JVM上运行得更快。 您很有可能会更新Java,然后重新编译源代码。

让我们对所有这些假设进行测试。 我写了一个小程序,标题为“ 有史以来最糟糕的分而治之原则add128()接受128个参数(!),并两次调用add64() -参数的前半部分和后半部分。 add64()类似于,只是它两次调用add32() 。 我想您会明白的,最后我们可以使用add2()进行繁重的工作。 一些数字被截断以免引起您的注意 :

public class ConcreteAdder {public int add128(int x1, int x2, int x3, int x4, ... more ..., int x127, int x128) {return add64(x1, x2, x3, x4, ... more ..., x63, x64) +add64(x65, x66, x67, x68, ... more ..., x127, x128);}private int add64(int x1, int x2, int x3, int x4, ... more ..., int x63, int x64) {return add32(x1, x2, x3, x4, ... more ..., x31, x32) +add32(x33, x34, x35, x36, ... more ..., x63, x64);}private int add32(int x1, int x2, int x3, int x4, ... more ..., int x31, int x32) {return add16(x1, x2, x3, x4, ... more ..., x15, x16) +add16(x17, x18, x19, x20, ... more ..., x31, x32);}private int add16(int x1, int x2, int x3, int x4, ... more ..., int x15, int x16) {return add8(x1, x2, x3, x4, x5, x6, x7, x8) + add8(x9, x10, x11, x12, x13, x14, x15, x16);}private int add8(int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8) {return add4(x1, x2, x3, x4) + add4(x5, x6, x7, x8);}private int add4(int x1, int x2, int x3, int x4) {return add2(x1, x2) + add2(x3, x4);}private int add2(int x1, int x2) {return x1 + x2;}}

不难看出,通过调用add128()我们总共进行了127个方法调用。 很多。 仅供参考,这里是一个简单的实现 :

public class InlineAdder {public int add128n(int x1, int x2, int x3, int x4, ... more ..., int x127, int x128) {return x1 + x2 + x3 + x4 + ... more ... + x127 + x128;}

最后,我还提供了一个使用abstract方法和继承的实现。 127个虚拟呼叫非常昂贵。 这些方法需要动态调度 ,因此要求更高,因为它们无法内联。 不能吗

public abstract class Adder {public abstract int add128(int x1, int x2, int x3, int x4, ... more ..., int x127, int x128);public abstract int add64(int x1, int x2, int x3, int x4, ... more ..., int x63, int x64);public abstract int add32(int x1, int x2, int x3, int x4, ... more ..., int x31, int x32);public abstract int add16(int x1, int x2, int x3, int x4, ... more ..., int x15, int x16);public abstract int add8(int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8);public abstract int add4(int x1, int x2, int x3, int x4);public abstract int add2(int x1, int x2);
}

和一个实现:

public class VirtualAdder extends Adder {@Overridepublic int add128(int x1, int x2, int x3, int x4, ... more ..., int x128) {return add64(x1, x2, x3, x4, ... more ..., x63, x64) +add64(x65, x66, x67, x68, ... more ..., x127, x128);}@Overridepublic int add64(int x1, int x2, int x3, int x4, ... more ..., int x63, int x64) {return add32(x1, x2, x3, x4, ... more ..., x31, x32) +add32(x33, x34, x35, x36, ... more ..., x63, x64);}@Overridepublic int add32(int x1, int x2, int x3, int x4, ... more ..., int x32) {return add16(x1, x2, x3, x4, ... more ..., x15, x16) +add16(x17, x18, x19, x20, ... more ..., x31, x32);}@Overridepublic int add16(int x1, int x2, int x3, int x4, ... more ..., int x16) {return add8(x1, x2, x3, x4, x5, x6, x7, x8) + add8(x9, x10, x11, x12, x13, x14, x15, x16);}@Overridepublic int add8(int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8) {return add4(x1, x2, x3, x4) + add4(x5, x6, x7, x8);}@Overridepublic int add4(int x1, int x2, int x3, int x4) {return add2(x1, x2) + add2(x3, x4);}@Overridepublic int add2(int x1, int x2) {return x1 + x2;}}

在有关@Cacheable开销的文章发表后,受到一些有趣的读者意见的鼓励,我编写了一个快速基准测试,以比较过度提取的ConcreteAdderVirtualAdder开销(以查看虚拟调用开销)。 结果出乎意料,有些模棱两可。 我在两台机器(蓝色和红色),相同的软件上运行相同的基准测试,但是第二台具有更多内核,并且是64位:
图表

详细环境:

环境环境

事实证明,在一台速度较慢的计算机上 JVM决定内联所有内容。 不仅简单的private通话,而且虚拟的通话一次。 那怎么可能 很好,JVM发现Adder只有一个子类,因此每个abstract方法只有一个可能的版本。 如果在运行时加载另一个子类(或什至更多子类),则可能会看到性能下降,因为不再可能进行内联。 但是抛开细节,在此基准测试方法中调用并不便宜,实际上是免费的 ! 方法调用(具有极大的文档价值,提高了可读性)仅存在于源代码和字节码中。 在运行时,它们被完全消除(内联)。

我不太理解第二个基准。 看起来机器B的运行速度确实更快,但实际上运行参考SingleMethodCall基准测试的速度更快,但其他机器甚至比A都慢。 也许它决定推迟内联? 差异是显着的,但并不是那么大。 同样,就像优化堆栈跟踪生成一样,如果您开始通过手动内联方法来优化代码,从而使它们变得更长和复杂,那么您正在解决错误的问题。

该基准可在GitHub上获得 ,以及文章来源 。 我鼓励您在设置中运行它。 此外,每个拉取请求都是自动在Travis上构建的,因此您可以在同一环境下轻松比较结果。

参考: 方法在JVM中内联的积极性如何? 来自我们的JCG合作伙伴 Tomasz Nurkiewicz,来自Java和邻里博客。

翻译自: https://www.javacodegeeks.com/2013/02/how-aggressive-is-method-inlining-in-jvm.html

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

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

相关文章

Python正则表达式基础

1. 正则表达式基础 1.1. 简单介绍 正则表达式并不是Python的一部分。正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大。得益于这一点,在提供了正则表…

Java并发:隐藏线程死锁

大多数Java程序员熟悉Java线程死锁概念。 它本质上涉及2个线程,它们彼此永远等待。 这种情况通常是平面(同步)或ReentrantLock(读或写)锁排序问题的结果。 Found one Java-level deadlock:"pool-1-thread-2"…

vue中使用axios发送请求

我们知道,vue2.0以后,vue就不再对vue-resource进行更新,而是推荐axios,而大型项目都会使用 Vuex 来管理数据,所以这篇博客将结合两者来发送请求 1.安装axios cnpm i axios -S 2.方案一:修改原型链 首先&…

django缓存

由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5分钟内…

linux 输入法成繁体字_寻找Ubuntu中繁体字输入法

当客户来自港台地区时,英文和繁体字就成了交流的主要工具。windows下我们有搜狗输入法可以切换简体与繁体,那么Ubuntu下怎么办?這是我第一次考慮這個問題,在我的印象裏Linux下的中文輸入法還不是那麼完善,所以我進行了…

vue跨域解决方法

vue跨域解决方法 vue项目中,前端与后台进行数据请求或者提交的时候,如果后台没有设置跨域,前端本地调试代码的时候就会报“No Access-Control-Allow-Origin header is present on the requested resource.” 这种跨域错误。 要想本地正常的调…

CSS盒子模型之详解

前言: 盒子模型是css中最核心的基础知识,理解了这个重要的概念才能更好的排版,进行页面布局。一、css盒子模型概念 CSS盒子模型 又称框模型 (Box Model) ,包含了元素内容(content)、内边距&#…

LeetCode的二分查找的练习部分总结

这两天由于工作的原因,一直没有写博客,但是却把LeetCode上面的题目做了不少——二分查找。上面这些题都是这两天写的。现在简单做一个总结。 首先二分查找的思想就是对一个有规律的元素(事情)进行不断的排除,最后找到符…

在Mac上安装IntelliJ IDEA

在Mac上安装IntelliJ IDEA http://www.jetbrains.com/idea/documentation/ 入门视频 这篇文章旨在介绍如何在Mac系统上安装IntelliJ IDEA,至于IntelliJ IDEA的介绍和使用方法,大家另行查阅,本篇的文章不再详细阐述。 简短解说,I…

行内元素,块级元素,各自特点及其相互转化

作为一名小前端,块级元素、行内元素用了几千几万次,除了“块级独占一行,行内不独占”之外,对细节属性的了解十分匮乏,今天做以部分属性的测试和阐述。 一、 对物理属性的支持 元素类别widthheightpaddingmargin是否独…

从RSS Feed和YQL创建数据表

Yahoo Query Language( YQL )是一种查询语言,例如SQL。 使用YQL,我们可以跨Web服务 查询 , 过滤和联接数据。 YQL也可以阅读RSS feed。 响应可以是JSON或XML。 雅虎提供了一个YQL控制台,用于调试 &…

Qt之QSS(Q_PROPERTY-自定义属性)

版权声明:进步始于交流,收获源于分享!纯正开源之美,有趣、好玩、靠谱。。。作者:一去丶二三里 博客地址:http://blog.csdn.net/liang19890820 目录(?)[] 简述 在Qt之QSS(Q_PROPERTY-原始属性&a…

python print error 空_python笔记37:10分钟掌握异常处理,再也不担心程序挂了

主要内容:小目标:异常处理主要内容:错误与异常,try_except语句对于撸代码的程序员来说,程序运行中出现问题是常见的现象;实际学习与工作中,我们会花很大的精力去解决各种问题;1. 程序…

contenteditable元素的placeholder输入提示语设置

在某些情况下,textarea是不够用的,我们还需要显示一些图标或者高亮元素,这就需要用富文本编辑器,而富文本编辑器本质上是HTML元素设置了contenteditable。 然后可能需要像input、textarea有placeholder的输入提示语,但…

css 浮动和清除浮动

在写页面布局的过程中,浮动是大家经常用的属性。在好多的排版布局中都是用的的浮动比如说下面这些地方都是应用到了浮动。 在我学习浮动的时候可是熬坏了脑筋,在这里我分享一下我对浮动这块知识的总结。 一、浮动的定义 使元素脱离文档流,按…

Java Code Geeks和Packt提供的Hadoop书籍赠品

亲爱的极客,由于参与度很高,并且为了有机会赢得尽可能多的Hadoop粉丝,我们决定将竞赛延长一个星期,直到下周二。 各位极客, 赠品在Java Code Geeks上继续。 我们很高兴地宣布,我们再次与Packt Publishin…

【转】 差分约束系统详解(转化为最短路) (概念)

---恢复内容开始--- 转自:http://www.cnblogs.com/void/archive/2011/08/26/2153928.html 差分约束系统中: 如果求未知数的最大值,那么按小于等于建图后求最短路即可。(因为求最短路是由无穷向下约束而得到的,所以得到…

【HTML基础】表格和表单

本次博客的主要内容如下: meta和link表格表单 meta和link meta meta的属性有两种:name和http-equiv。 name属性主要用于描述网页内容,对应与网页内容。 1.关键字,当搜索引擎在爬取内容的时候,会根据关键字判断&a…

python json 不好用_Python之json使用

一、概念json是一种通用的数据类型,任何语言都认识接口返回的数据类型都是json长得像字典,形式也是k-v { }其实json是字符串字符串不能用key、value来取值,要先转成字典才可以格式如下:{"error_code": 0,#要使用双引号&…

jstack命令(Java Stack Trace)

转:http://blog.csdn.net/fenglibing/article/details/6411940 JDK内置工具使用 一、javah命令(C Header and Stub File Generator) 二、jps命令(Java Virtual Machine Process Status Tool) 三、jstack命令(Java Stack Trace) 四、jstat命令(Java Virtual Machine …