太快了,太变态了:什么会影响Java中的方法调用性能?

那么这是怎么回事?

让我们从一个简短的故事开始。 几周前,我提议对Java核心libs邮件列表进行更改 ,以覆盖当前final一些方法。 这刺激了一些讨论主题-其中之一是其中一个性能回归通过采取这是一个方法被引入的程度final免遭停止它final

我对是否会出现性能下降有一些想法,但是我将这些想法放在一边,试图询问是否有关于此主题的合理基准。 不幸的是我找不到任何东西。 这并不是说它们不存在,也没有其他人没有对此情况进行调查,但是我没有看到任何经过公共同行评审的代码。 所以–是时候写一些基准了。

标杆管理方法

因此,我决定使用功能强大的JMH框架来汇总这些基准。 如果您不相信框架会帮助您获得准确的基准测试结果,那么您应该看一下编写框架的Aleksey Shipilev的演讲 ,或者Nitsan Wakart的非常酷的博客文章,其中解释了它如何提供帮助。

就我而言,我想了解是什么影响了方法调用的性能。 我决定尝试各种不同的方法调用并衡量成本。 通过设置一组基准并一次仅更改一个因素,我们可以单独排除或了解不同因素或因素组合如何影响方法调用成本。

内联

flat_cat 让我们压缩这些方法的调用方法。

同时,最明显的影响因素是根本没有方法调用! 方法调用的实际成本有可能完全被编译器优化。 从广义上讲,有两种降低通话成本的方法。 一种是直接内联方法本身,另一种是使用内联缓存。 不用担心-这些是非常简单的概念,但是需要引入一些术语。 假设我们有一个名为Foo的类,它定义了一个名为bar的方法。

class Foo {void bar() { ... }
}

我们可以通过编写如下代码来调用bar方法:

Foo foo = new Foo();
foo.bar();

这里重要的是实际调用bar的位置– foo.bar() –称为callsite 。 当我们说一个方法被“内联”时,这意味着该方法的主体已被取而代之以进入方法,而不是方法调用。 对于由许多小方法组成的程序(我认为是一个适当分解的程序),内联可以导致明显的加速。 这是因为该程序并没有花费大部分时间来调用方法,而是没有实际执行工作! 通过使用CompilerControl批注,我们可以控制方法是否在JMH中内联。 稍后我们将回到内联缓存的概念。

层次深度和覆盖方法

IMG_3015 父母会放慢孩子的速度吗?

如果我们选择从方法中删除final关键字,则意味着我们将能够覆盖它。 因此,这是我们需要考虑的另一个因素。 因此,我采用了方法并在类层次结构的不同级别上调用了它们,并且还具有在层次结构的不同级别上被覆盖的方法。 这使我能够了解或消除深层次的层次结构如何影响成本。

多态性

多态性 动物:如何描述任何面向对象的概念。

当我较早提到呼叫站点的想法时,我偷偷地避免了一个相当重要的问题。 由于可以在子类中覆盖非final方法,因此我们的调用站点final可能会调用不同的方法。 因此,也许我传入了Foo或它的孩子Baz,它也实现了bar()。 您的编译器如何知道要调用的方法? 默认情况下,方法是Java中的虚拟(可重写)方法,它必须为每个调用在称为vtable的表中查找正确的方法。 这非常慢,因此优化编译器总是试图减少所涉及的查找成本。 我们前面提到的一种方法是内联,如果您的编译器可以证明在给定的调用站点只能调用一种方法,则该方法非常有用。 这称为单态呼叫站点。

不幸的是,证明呼叫站点是单态性所需的许多时间分析最终可能是不切实际的。 JIT编译器倾向于采用另一种方法来分析在调用站点上调用的类型,并猜测如果该调用站点在前N个调用中是单态的,则基于它始终将是单态的假设,值得进行推测性优化。 这种推测性优化通常是正确的,但是由于并不总是正确的,因此编译器需要在方法调用之前注入防护以检查方法的类型。

不过,单态调用站点并不是我们要优化的唯一情况。 许多调用站点被称为双态的 -可以调用两种方法。 您仍然可以内联双态呼叫站点,方法是使用保护代码检查要调用的实现,然后跳转到该实现。 这仍然比完整方法调用便宜。 也可以使用内联缓存来优化这种情况。 内联缓存实际上并不将方法主体内联到调用站点中,但它具有专门的跳转表,其作用类似于完整vtable查找上的缓存。 热点JIT编译器支持双态内联高速缓存,并声明具有3个或更多可能实现的任何呼叫站点都是megamorphic的

这为我们划分了3种调用情况,以进行基准测试:单态,双态和超态。

结果

让我们对结果进行分组,以便更轻松地从树木中查看木材。我介绍了原始数字以及围绕它们的一些分析。 实际的数量/成本并不是那么重要。 有趣的是,不同类型的方法调用之间的比率以及相关的错误率很低。 最快和最慢之间存在很大的差异– 6.26倍。 实际上,由于与测量空方法的时间相关的开销,差异可能更大。

这些基准的源代码可在github上找到 。 为了避免混淆,并没有全部显示结果。 最后,多态基准来自运行PolymorphicBenchmark ,而其他基准来自JavaFinalBenchmark

简单的呼叫网站

Benchmark                                                    Mode   Samples         Mean   Mean error    Units
c.i.j.JavaFinalBenchmark.finalInvoke                         avgt        25        2.606        0.007    ns/op
c.i.j.JavaFinalBenchmark.virtualInvoke                       avgt        25        2.598        0.008    ns/op
c.i.j.JavaFinalBenchmark.alwaysOverriddenMethod              avgt        25        2.609        0.006    ns/op

我们的第一组结果比较了虚拟方法, final方法和层次结构较深且被覆盖的方法的调用成本。 请注意,在所有这些情况下,我们都强制编译器不内联方法。 正如我们所看到的,时间之间的差异很小,而且我们的平均错误率表明它并不重要。 因此,我们可以得出结论,仅添加final关键字并不会大大提高方法调用的性能。 覆盖该方法似乎也没有太大区别。

内联简单的呼叫站点

Benchmark                                                    Mode   Samples         Mean   Mean error    Units
c.i.j.JavaFinalBenchmark.inlinableFinalInvoke                avgt        25        0.782        0.003    ns/op
c.i.j.JavaFinalBenchmark.inlinableVirtualInvoke              avgt        25        0.780        0.002    ns/op
c.i.j.JavaFinalBenchmark.inlinableAlwaysOverriddenMethod     avgt        25        1.393        0.060    ns/op

现在,我们采用了相同的三种情况,并删除了内联限制。 同样, final和虚拟方法调用的结束时间彼此相似。 它们比非内联情况快大约4倍,我将其归结为内联本身。 在此始终被覆盖的方法调用最终在两者之间。 我怀疑这是因为方法本身具有多个可能的子类实现,因此编译器需要插入类型保护。 上面在“多态”下对此进行了详细解释。

类等级冲击

Benchmark                                                    Mode   Samples         Mean   Mean error    Units
c.i.j.JavaFinalBenchmark.parentMethod1                       avgt        25        2.600        0.008    ns/op
c.i.j.JavaFinalBenchmark.parentMethod2                       avgt        25        2.596        0.007    ns/op
c.i.j.JavaFinalBenchmark.parentMethod3                       avgt        25        2.598        0.006    ns/op
c.i.j.JavaFinalBenchmark.parentMethod4                       avgt        25        2.601        0.006    ns/op
c.i.j.JavaFinalBenchmark.inlinableParentMethod1              avgt        25        1.373        0.006    ns/op
c.i.j.JavaFinalBenchmark.inlinableParentMethod2              avgt        25        1.368        0.004    ns/op
c.i.j.JavaFinalBenchmark.inlinableParentMethod3              avgt        25        1.371        0.004    ns/op
c.i.j.JavaFinalBenchmark.inlinableParentMethod4              avgt        25        1.371        0.005    ns/op

哇–这是很多方法! 每个编号的方法调用(1-4)表示调用方法的类层次结构有多深。 所以parentMethod4意味着我们调用了在该类的第4个父级上声明的方法。 如果看一下数字,则1和4之间的差异很小。因此,我们可以得出结论,层次深度没有区别。 可内联的案例都遵循相同的模式:层次深度没有区别。 我们的inlineable方法性能与inlinableAlwaysOverriddenMethod相当,但比inlinableVirtualInvoke慢。 我再次将其归结为所使用的类型防护。 JIT编译器可以对方法进行概要分析,以找出仅内联的一种方法,但无法证明这是永远存在的。

类层次对

Benchmark                                                    Mode   Samples         Mean   Mean error    Units
c.i.j.JavaFinalBenchmark.parentFinalMethod1                  avgt        25        2.598        0.007    ns/op
c.i.j.JavaFinalBenchmark.parentFinalMethod2                  avgt        25        2.596        0.007    ns/op
c.i.j.JavaFinalBenchmark.parentFinalMethod3                  avgt        25        2.640        0.135    ns/op
c.i.j.JavaFinalBenchmark.parentFinalMethod4                  avgt        25        2.601        0.009    ns/op
c.i.j.JavaFinalBenchmark.inlinableParentFinalMethod1         avgt        25        1.373        0.004    ns/op
c.i.j.JavaFinalBenchmark.inlinableParentFinalMethod2         avgt        25        1.375        0.016    ns/op
c.i.j.JavaFinalBenchmark.inlinableParentFinalMethod3         avgt        25        1.369        0.005    ns/op
c.i.j.JavaFinalBenchmark.inlinableParentFinalMethod4         avgt        25        1.371        0.003    ns/op

这遵循与上述相同的模式final关键字似乎没有什么区别。 我会认为这是可能在这里,从理论上说,对于inlinableParentFinalMethod4来加以证明inlineable没有型后卫,但它不会出现这种情况。

多态性

Monomorphic: 2.816 +- 0.056 ns/op
Bimorphic: 3.258 +- 0.195 ns/op
Megamorphic: 4.896 +- 0.017 ns/op
Inlinable Monomorphic: 1.555 +- 0.007 ns/op
Inlinable Bimorphic: 1.555 +- 0.004 ns/op
Inlinable Megamorphic: 4.278 +- 0.013 ns/op

最后,我们来谈谈多态调度的情况。 单态调用成本与上面的常规虚拟调用成本大致相同。 由于我们需要在较大的vtable上进行查找,因此随着双态和多态情况的显示,它们变得更慢。 一旦启用内联类型分析,我们的单态和双态调用站点就会降低我们的“内联守卫”方法调用的成本。 因此与类层次结构的情况类似,只是速度稍慢一些。 大形情况仍然非常缓慢。 请记住,我们这里没有告诉热点防止内联,只是它没有为比双态更复杂的调用站点实现多态内联缓存。

我们学到了什么?

我认为值得一提的是,有很多人没有表现心理模型来说明花费时间不同的不同类型的方法调用,还有很多人知道他们花费的时间不同,但实际上并没有非常正确。 我知道我以前去过那里,做了各种各样的错误假设。 因此,我希望这项调查对人们有所帮助。 这是我很乐意支持的声明摘要。

  • 最快和最慢的方法调用类型之间有很大的不同。
  • 在实践中,添加或删除final关键字并不会真正影响性能,但是,如果您随后重构层次结构,事情可能会开始放慢速度。
  • 更深的类层次结构对呼叫性能没有真正的影响。
  • 单态调用比双态调用更快。
  • 双态调用比大形调用快。
  • 在概要分析(但不是可证明)的情况下,我们看到的类型防护在单态调用站点上确实使速度放慢了很多。

我会说类型保护程序的成本是我个人的“重大启示”。 这是我鲜为人知的话题,经常被认为是无关紧要的。

注意事项和进一步工作

当然,这不是主题领域的最终决定!

  • 该博客仅关注与方法调用性能有关的类型相关因素。 我没有提到的一个因素是由于主体大小或调用堆栈深度而导致的围绕方法内联的启发式方法。 如果您的方法太大,则根本不会内联,您仍然要为方法调用的费用付费。 编写小的,易于阅读的方法的另一个原因。
  • 我没有研究过接口调用如何影响这些情况。 如果您发现这很有趣,那么可以在Mechanical Sympathy博客上研究调用接口的性能。
  • 我们在这里完全忽略的一个因素是方法内联对其他编译器优化的影响。 当编译器执行仅考虑一种方法的优化(过程内优化)时,他们实际上希望获得尽可能多的信息以进行有效的优化。 内联的局限性可以大大缩小其他优化必须使用的范围。
  • 将说明直接附加到汇编级别,以深入了解该问题。

也许这些是将来博客文章的主题。

翻译自: https://www.javacodegeeks.com/2014/05/too-fast-too-megamorphic-what-influences-method-call-performance-in-java.html

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

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

相关文章

1、dubbo的概念

Dubbo是什么? Dubbo是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000个服务提供3,000,000,000次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。Dubbo[]是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用…

轻云服务器的性能,腾讯云轻量应用服务器性能评测(以香港地域为例)

腾讯云轻量应用服务器香港节点24元/月,价格很不错,ForeignServer来说说腾讯云轻量服务器香港地域性能评测,包括腾讯云轻量应用服务器CPU型号配置、网络延迟速度测试:腾讯云香港轻量应用服务器性能评测腾讯云轻量应用服务器地域可选…

vue2.5.2版本 :MAC设置应用在127.0.0.1:80端口访问; 并将127.0.0.1指向www.yours.com ;问题“ Invalid Host header”

0.设置自己的host文件,将127.0.0.1指向自己想要访问的域名 127.0.0.1 www.yours.com 1.MAC设置应用在127.0.0.1:80端口访问: config/index.js目录下修改host和port 然后sudo运行npm run dev:(mac的80端口是被自身分享应用占用的&#xff0c…

Google Android 平台正式开源

Google 推出移动设备软件平台 Android 之时,曾向开发者开放 SDK 包,并许诺将在开源许可模式下开放其全部代码,今天,Google 与其合作伙伴,在 Open Handset Alliance 兑现了其承诺,用户现在可以正式下载 Andr…

JSP彩色验证码

产生验证码图片的文件-----image.jsp <% page contentType"image/jpeg" import"java.awt.*,java.awt.image.*,java.util.*,javax.imageio.*" %><%!Color getRandColor(int fc,int bc){//给定范围获得随机颜色 Random random new Random()…

自定义Windows右击菜单调用Winform程序

U9_Git中ignore文件处理 背景 U9代码中有许多自动生成的文件&#xff0c;不需要上传Git必须BE Entity中的.target文件 .bak 文件 Enum.cs结尾的文件&#xff0c;还有许多 extand文件。 这些文件都不需要上传Git。 但是这些文件太多了&#xff0c;不可能手动加入到ignore文件中。…

Gradle Introduction

目录 Compileing development ProcessOld compile MothedModern compile MothedWhat is GradleGradle EffectWhat is GroovyGroovy syntax relesStructure ScriptPeojectAttributeTaskExample #1applyExample #1Example #2Dependency ManagementExample #1Example #2Multiple P…

u8系统怎么连接服务器,用友U8 怎么连接远程服务器

用友U8 怎么连接远程服务器 内容精选换一换配置应用系统的跨云热备容灾方案如图1所示。在如图1所示的方案中&#xff0c;用户的生产数据中心的应用系统使用MySQL作为数据库&#xff0c;应用系统与MySQL均热备容灾到华为云上。用户的生产数据中心与华为云之间使用专线进行网络连…

[导入]商业智能2.0?(BI 2.0 from Timo Elliott)

译者注: 关于BI2.0的说法很多&#xff0c;不尽一致&#xff0c;目的只是想多了解一些&#xff1b;译文并不代表译者认可原文观点&#xff0c;只是顺便译了以方便不喜欢E文的朋友。本文是一篇充满探讨及疑问的文章&#xff0c;来自Timo Elliott(Business Objects历史上的第8号员…

angularjs封装bootstrap官网的时间插件datetimepicker

背景:angular与jquery类库的协作 第三方类库中&#xff0c;不得不提的是大名鼎鼎的jquery,现在基本上已经是国内web开发的必修工具了。它灵活的dom操作&#xff0c;让很多web开发人员欲罢不能。再加上已经很成熟的jquery UI 库和大量jquery 插件&#xff0c;几乎是一个取之不尽…

Java中的得墨meter耳定律–最少知识原理–实际示例

得墨meter耳定律&#xff08;也称为最少知识定律&#xff09;是一种编码原理&#xff0c;它表示模块不应该知道其操作的对象的内部细节。 如果代码依赖于特定对象的内部细节&#xff0c;则很有可能一旦该对象的内部发生更改&#xff0c;它就会被破坏。 由于封装是关于隐藏对象的…

课后作业1

自我介绍 我叫张阔&#xff0c;我的爱好是旅行&#xff0c;游览世界的美好风光&#xff1b; 我的码云个人主页是&#xff1a;https://gitee.com/ZkTt0428&#xff1b; 我的第一个项目地址是&#xff1a;https://gitee.com/ZkTt0428/Frist&#xff1b; 目前代码量有10000行了&am…

针对新手的Java EE7和Maven项目-第4部分-定义Ear模块

从前面的部分恢复 第1部分 第2部分 第3部分 我们正在恢复第四部分&#xff0c;目前我们的简单项目有 Web Maven模块&#xff08;战争&#xff09; 一个ejb模块&#xff08;ejb&#xff09;&#xff0c;其中包含我们的无状态会话bean&#xff08;EJB 3.1&#xff09; 第二…

最大公因数和最小公倍数

一丶 最大公因数求法&#xff1a;辗转相除法(也称欧几里得算法)原理: 二丶最小公倍数求法&#xff1a;两个整数的最小公倍数等于两整数之积除以最大公约数1 #include <iostream>2 3 using namespace std;4 5 //辗转相除法(欧几里得算法)6 7 int gcd(int a, int b)8 {9…

css实现div内一段文本的两端对齐

在一个固定宽度的div内&#xff0c;使得P标签内的文本两端对齐&#xff1a; text-align: justify;text-justify:inter-ideograph; <!DOCTYPE html><html lang"en"><head><meta charset"UTF-8"><title>justify</title>…

QT学习三 标准对话框 QMessageBox

QMessageBox内置了几种static方法,例如 QMessageBox::question() 返回值:StandardButton 参数:QWidget * 父窗口&#xff0c;标题名&#xff0c;内容&#xff0c;按钮 YES|NO,默认选中按钮) 示例: 1 #include "mainwindow.h"2 #include <QApplication>3 #incl…

react学习笔记2

1.build文件介绍 &#xff08;1&#xff09;react.js 是react的核心库 &#xff08;2&#xff09;react-dom.js 提供与DOM相关功能 &#xff08;3&#xff09;browser.js 是将JSX语法转为javascript语法 2.组件的继续学习 注意&#xff1a;组件的第一个字母必须大写&…

HOW-TO:带有MySQL的JEE应用程序中具有集群功能的Quartz Scheduler

Quartz Scheduler是Java世界中最流行的调度库之一。 过去&#xff0c;我主要在Spring应用程序中使用Quartz。 最近&#xff0c;我一直在研究将在云中部署的JBoss 7.1.1上运行的JEE 6应用程序中的调度。 我考虑的一种选择是Quartz Scheduler&#xff0c;因为它提供了与数据库的集…

C语言使用scanf()函数时,%c前面和后面分别加上空格后的结果

在使用scanf()读取输入的字符时&#xff0c;当转换说明为%c时&#xff0c;"%c"、" %c"、"%c " 这三种不同的写法&#xff0c;对数据读取的结果有什么影响吗&#xff0c;答案是肯定的&#xff0c;%c 加不加空格&#xff0c;空格在前还是在后&am…

Python -- 自动导入所需要的模块

try: import xlwtexcept ImportError as e:   import os   print(e)   os.system("pip install xlwt")转载于:https://www.cnblogs.com/xlx12138/p/10551894.html