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

方法内联在JVM中有多积极?相关推荐

  1. jvm 方法内联_方法内联在JVM中有多积极?

    jvm 方法内联 在IntelliJ IDEA中使用Ctrl + Alt + M 提取方法 . Ctrl + Alt + M. 这就像选择一段代码并按此组合一样简单. Eclipse也有它 . 我讨厌 ...

  2. jit 方法内联_JIT编译器,内联和转义分析

    jit 方法内联 即时(JIT) 即时(JIT)编译器是Java虚拟机的大脑. JVM中对JIT编译器的影响最大. 一会儿,让我们退后一步,看看已编译和未编译语言的示例. 诸如Go,C和C ++之类的 ...

  3. java内联函数,JVM中的步骤内联

    JVM中的方法内联 在C++中,可以明确定义内联函数,使用inline关键字.在Java中不能定义内联函数,但是方法的内联在JIT编译中还是存在的,只不过是JIT自动优化的,我们无法在写代码的时候指定 ...

  4. java 内联调用深度_Java中内联虚拟方法调用的性能

    java 内联调用深度 总览 动态编译的好处之一是它能够支持在虚拟方法代码上的广泛方法内联. 内联代码可提高性能时,代码仍必须检查类型(以防由于优化而更改了类型)或在多个可能的实现之间进行选择. 这导 ...

  5. Java内联虚拟方法调用的性能

    总览 动态编译的好处之一是,它能够支持在虚拟方法代码上进行广泛的方法内联. 虽然内联代码可以提高性能,但是代码仍然必须检查类型(以防由于优化而更改了类型)或在多个可能的实现之间进行选择. 这导致了问题 ...

  6. 【JVM】JVM 内联优化

    文章目录 1.概念 2.过程 3.场景 4.案例 5.案例2 6.java内联配置 7.方法内联的规则 8.案例 8.1 内联案例1 1.概念 内联概念:把函数调用的方法直接内嵌到方法内部,减少函数调 ...

  7. 安卓TV插件化9.0内联崩溃原因及解决方案

    安卓 TV 端应用的更新比较困难,一方面是受限于各个设备厂商的规则,应用更新策略比较慢,另一方面是 TV 用户主动更新的意愿比较低.因此插件化热更新在安卓 TV 端就成为了有效更新应用业务能力的必要技 ...

  8. 内联(inlining)

    JVM JIT编译器优化技术有近100中,其中最最重要的方式就是内联(inlining). 方法内联可以省掉方法栈帧的创建,方法内联还使让JIT编译器更多更深入的优化变成可能. Inlining相关的 ...

  9. 提高C++性能的编程技术笔记:内联+测试代码

    内联类似于宏,在调用方法内部展开被调用方法,以此来代替方法的调用.一般来说表达内联意图的方式有两种:一种是在定义方法时添加内联保留字的前缀:另一种是在类的头部声明中定义方法. 虽然内联方法的调用方式和 ...

最新文章

  1. [转] java的 Collection 和 Map 详解
  2. python自动化测试框架pytest.pdf_Python自动化测试框架
  3. 10进制转换成16进制
  4. BASH 比较运算小结[转载 小蜗牛五二]
  5. 刺客信条奥德赛缺少dll文件_《刺客信条 奥德赛》免费归来,单人冒险暗杀游戏,搞一搞喽...
  6. tomcat,httpd 日志格式说明
  7. 微信小程序云开发教程-云函数入门(1)-开发步骤
  8. 数据集标签_数据分享 | LiDAR点云数据汇总
  9. 阶段3 3.SpringMVC·_06.异常处理及拦截器_5 SpringMVC拦截器之编写controller
  10. 【计算机网络】笔记2——三种交换方式:电路交换,分组交换,报文交换
  11. 服务器安全防护和保护措施方案-数据湾
  12. 算法的时间复杂度、渐进表达式、渐进性分析和渐进符号(O、Ω、θ、o、ω)
  13. 蓝凌OA前台任意文件读取漏洞利用
  14. skywalking-介绍
  15. 搭建LDAP服务器详细流程
  16. 开源项目SMSS开发指南(二)——基于libevent的线程池
  17. 哈夫曼树 (100分)哈夫曼树
  18. 高通Sensor校准
  19. Unity第一人称手游-左侧控制移动,右侧控制视角和方向(第一人称,类似吃鸡游戏)
  20. VMware虐我千百遍,我却待她如初恋

热门文章

  1. 哈希值 哈希表_哈希杰森
  2. 空调吸气和排气_吸气剂和二传手被认为有害
  3. dynamodb java_使用Java扫描DynamoDB项目
  4. javafx窗体程序_JavaFX真实世界应用程序:欧洲电视网广播联盟
  5. jvm gc停顿_在JVM中记录世界停顿
  6. junit单元测试断言_简而言之,JUnit:单元测试断言
  7. activemq网络桥接_ActiveMQ –经纪人网络解释–第3部分
  8. hibernate 调试_Hibernate调试–查找查询的来源
  9. java8 函数式编程_您必须学习Java 8的函数式编程吗?
  10. guava的正确引入方式_使用Guava的AbstractInvocationHandler正确完成代理