太快了,太变态了:什么会影响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,一经查实,立即删除!

相关文章

mac中apache服务器及虚拟主机配置

输入 sudo apachectl start,这样Apache就启动了。打开Safari浏览器地址栏输入 “http://localhost”,可以看到内容为“It works!”的页面。其位于“/Library(资源库)/WebServer/Documents/”下,这就是Apache的默认根目…

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…

在Spring MVC应用程序中使用Bean Validation 1.1获得更好的错误消息

在许多新功能中, Bean Validation 1.1引入了使用统一表达式语言(EL)表达式的错误消息插值。 这允许基于条件逻辑来定义错误消息,还可以启用高级格式化选项 。 添加到Spring MVC应用程序后,您可以非常简单地显示更友好的…

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文件中。…

替换富文本里的px为rem

var content 23pxcontent content.replace(/(\d )px/g, function(s, t) {s s.replace(px, );var value parseInt(s) * 0.001; // 100px 1remreturn value "rem"; //0.23rem }); 更多专业前端知识&#xff0c;请上 【猿2048】www.mk2048.com

centos7服务器文件同步,centos7文件实时同步工具lsyncd

1.本地目录同步# yum install lsyncd# vi /etc/lsyncd.conf------ User configuration file for lsyncd.---- Simple example for default rsync, but executing moves through on the target.---- For more examples, see /usr/share/doc/lsyncd*/examples/---- (注释掉这行)s…

Gradle Introduction

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

命名空间的引用问题

1、using System.Data; 2、using System.Data.SqlClient; 添加引用的时候&#xff0c;只要添加System.Data “类型的初始值设定引发异常” “未能加载程序集或它的某一个依赖项”-------这种错误可能跟&#xff08;命名空间&#xff09;程序集的引用有关系 转载于:https://www.…

使用LinkedHashMap的Code4ReferenceList最近使用(LRU)实现

最近&#xff0c;我偶然发现了Java面试问题之一&#xff1a; “使用Java集合类实现最近使用的列表&#xff08;LRU&#xff09;缓存吗&#xff1f;” 如果您以前曾处理过类似的问题&#xff0c;那么对您来说真的很容易。 否则&#xff0c;您将开始考虑实现LRU缓存的最佳收集类…

sha1.js

function encodeUTF8(s) {var i, r [], c, x;for (i 0; i < s.length; i)if ((c s.charCodeAt(i)) < 0x80) r.push(c);else if (c < 0x800) r.push(0xC0 (c >> 6 & 0x1F), 0x80 (c & 0x3F));else {if ((x c ^ 0xD800) >> 10 0) //对四字节…

angular 获取ng-repeat完成状态 $last

$index $first $middle $last $odd $even html <ul><li ng-repeat"item in data" repeat-finish&#xff1d;"renderFinish()">{{item.str}}</li> </ul> 指令 app.directive(repeatFinish,function(){return {link: function(sco…

博客园-我的新的开始

励志学会,python,php-由于必须150字所以水一下 python: Python 是由 Guido van Rossum 在八十年代末和九十年代初&#xff0c;在荷兰国家数学和计算机科学研究所设计出来的。 Python 本身也是由诸多其他语言发展而来的,这包括 ABC、Modula-3、C、C、Algol-68、SmallTalk、Unix …

Selenium备忘手册 [转]

最近的项目准备用Selenium作一部分的Regression Test。在SpringSide里参考了一下&#xff0c;又下了个Selenium IDE玩玩&#xff0c;觉得还蛮容易上手&#xff0c;基本上不需要手动写测试代码。 但实操起来时面对各种复杂的页面情况遇到不少麻烦。感觉Selenium 的offical d…

js中7种继承的实现和优缺点

原型链继承 function Parent(){this.name kevin }Parent.prototype.getName function(){console.log(this.name) }function Child(){} Child.prototype new Parent()var child new Child() console.log(child.name) //kevin优点&#xff1a; 简单&#xff0c;容易实现缺点&…

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

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

使用Java 8 Streams进行编程对算法性能的影响

多年来&#xff0c;使用Java进行多范例编程已经成为可能&#xff0c;它支持面向服务&#xff0c;面向对象和面向方面的编程的混合。 带有lambda和java.util.stream.Stream类的Java 8是个好消息&#xff0c;因为它使我们可以将功能性编程范例添加到混合中。 确实&#xff0c;lam…