阅读更多

5顶
0踩

编程语言

转载新闻 哪些因素影响Java调用的性能?

2015-06-25 17:29 by 副主编 mengyidan1988 评论(2) 有6034人浏览
本文由ImportNew - elviskang 翻译自 voxxed
当时发生了什么?

这得从一个小故事说起。我在一个Java核心库的邮件列表中提交了一个修改 ——重写了一些本是 final 的方法。一石激起千层浪,这一改动引发了几番讨论。而其中一个讨论的话题是:调用一个去除 final 标记的方法,将导致哪种程度的性能下降(performance regression)。

我不能确定这一改变是否会导致性能下降,但当我决定将此暂时搁置一边,试着寻找在这个讨论里是否有人公布过任何相关的完整基准测试(sane benchmarks)时,结果空手而归。我不能肯定地说有关的基准测试是不存在的,或者说其他人没做过这方面的探讨。但我能肯定的是,在这里,连任何公开的代码评审都没有。唉,看来是时候写一个基准测试了。

基准测试的方法论

我决定选用一个相当不错的框架 —— JMH 来构建基准测试。如果你质疑它测试的准确性,那么建议你看下对这个框架作者(Aleksey Shipilev)的访谈,或者阅读一下由Nitsan Wakart撰写的一篇彰显此框架风采的博文。

现在,我想知道哪些因素影响了Java方法调用的性能。所以我决定以不同方式调用方法,并测算它们的性能开销。以单一变量为前提来构造一套基准测试,我便能逐个排除或确定,哪些因素或哪种组合会影响到方法调用的性能。

内联



让我们把这些方法调用点压扁

方法调用的有无,是一个影响程度既是最高又是最低的因素——对于编译器来说,彻底优化方法调用所带来的开销并非不可能,有两种方法可以实现这样的需求:直接内联该方法本身和使用内联缓存(inline cache)。千万别被引入的这些术语给吓倒——它们都是通俗易懂的。现在我们假设有一个叫Foo的类,该类定义了一个叫bar的方法:
class Foo {
  void bar() { ... }
}

我们以如下的方式调用bar方法:
Foo foo = new Foo();
foo.bar();

这里有一个重要的知识点:实际调用 bar 的位置,即 foo.bar(),称为调用点(callsite)。当我们说一个方法“被内联”,意指方法体被插入到了调用点的位置上,以代替方法调用。对于那些由许多短小的方法所构成的程序——我称之为被适当分解的程序——内联可以有效地提升性能。这是因为结束以后可以发现,程序并没有把所有时间用在方法调用上,实际上程序并没有工作!我们在JMH中可以借由 CompilerControl 注释控制一个方法是否被内联。关于内联缓存的概念,我稍后再来说明。

层次结构深度与重写子类方法



是因为父母让孩子慢下来了吗?

如果我们移除一个方法的 final 关键字,便意味着我们能够重写它。所以这是另一个在进行测试我们需要考虑的情况。我会选择在同一层次结构中不同层次的子类里调用一些方法,并且在这些方法里有一些是会被不同层次的子类重写的。这样的测试能让我们确定或排除深的层次结构是否影响到重写所带来的性能开销。

多态性



动物世界:多态是如何表现的

先前我提到调用点这一概念时,我偷偷地回避了一个相当重要的问题——因为在子类中可以重写一个非 final 方法,这使得调用点可以调用不同的方法。现假设我传入一个 Foo 的实例或一个重写了 bar 子类—— Baz的实例,编译器如何得知要调用哪一个 bar 方法呢?在默认情况下,方法将在Java中被虚拟化(可重写)。对于任一调用点,编译器需要在一个称为虚拟表(vtable)的表中寻找与其对应的方法。这是个非常耗时的过程,所以,能进行优化的编译器,总是会试图减少这种查询带来的开销。一种方法就是先前提到的内联,这的确是个良策,但前提是编译器能证明在给定的调用点上调用的方法唯一。而这样的调用点我们称为单态(monomorphic)调用点。

不幸的是,进行这种分析需要耗费大量时间。所以在实际过程中,确定一个调用点是否单态是个不太可取的方法。对此,JIT编译器倾向于使用一种替代方法:列出哪些类可以在此调用点被调用,接着根据之前的N个相同的调用猜测此调用点是否是单态的。以假定某个调用点永远为单态,来进行投机性质的优化往往是可取的行为。因为这样的优化往往都是正确的,但也因它无法确保永远正确,编译器需要在方法调用之前注入一个用于检查方法类型的防护机制。

除了单态的调用点以外,还有两种调用点我们希望对其进行优化。一种称为双态(bimorphic)调用点,在该点上有两个候选方法。对此你依然可以实现内联——借助防护代码,让其检测应调用哪一个方法,并引导程序跳转至内联在调用点的两个方法体中真正对应的那一个。这样的方式还是比查看所有虚拟表的方式要快得多。但在某些情况下,我们得利用内联缓存来进行优化。内联缓存需要借助一张特定的跳转表( jump table),这种表类似于对虚拟表查找做的一份缓存。hotsopt JIT编译器支持双态内联缓存,并定义那些拥有三个及三个以上候选方法的调用点为超多状态(megamorphic)调用点。

这就使得我在基准测试与探究当中,需要额外地把调用情况划分为三类:单态、双态、超多状态。
结果

让我们把结果分类组织,以便研究细节。我已经提供了统计产生的原始数据。但我们的兴趣点不应放在性能测试结果的具体数值上,而应是不同类型的方法调用的性能开销之间的比率以及各自的错误率是否够低。如果最快与最慢的结果之间比率为6.26,则说明这是一个显著性差异。由于测试时使用的是空方法(详见源代码),所以在实际应用中,这样的差异会更大。

你可以在 github上查看此次基准测试的源代码。为了避免产生困惑,待会所有的结果将分块显示。最后显示的多态的基准测试是在 PolymorphicBenchmark 类中进行,其它的则在 JavaFinalBenchmark 类中。

简单调用点



最先看到的的一组结果,是比较调用一个 virtual 方法、一个 final 方法和一个拥有很深的层级结构,同时被所有子类重写的方法所带来的开销。注意,调用这些方法的时候我们都强制编译器不要内联它们。我们可以看到:三者在时间花费上相差甚微,并且各自的误差率都小到可以忽略。对此我们可以断定,仅添加一个 final 关键字并不会大幅度提升调用性能,重写一个方法也不见得会带来什么影响。

内联简单调用



现在,我们在开启内联的情况下再来一次相同的测试。由结果可见,final 方法和 virtual 方法的时间花费依旧相近,并比在没有内联的情况下快了4倍,我将此归功于内联优化。相比而言,被所有子类重写的方法的结果可就没那么好看了。我推测这是由于此方法有多个子类实现,使得编译器必须插入一个类型保护。有关的细节我们将在研究多态性的结果时进行阐述。

类层次结构的影响



哇噢——这儿有好几个的方法!方法名称的编号(1~4)代表该方法调用的层次。因此,parentMethod4 表示我们调用的方法位于class的上面第四级。(译注:在源代码中该方法位于顶层的父类)。由此结果我们能断定,结构层次的深度对性能开销没有影响。在开启内联的实例中,结论也是一样。这个测试中,被内联的方法的性能与 inlinableAlwaysOverriddenMethod 相当,但稍逊于 inlinableVirtualInvoke。我依旧认为这与使用了类型保护有关。事实上JIT编译器能剖析所有候选方法,从而只内联对应的那一个,但这并不证明它总会这么干。

类的层级结构对final方法的影响



该测试的结论与第一个测试一样 —— final 关键字不会产生任何影响。我本以为该测试将证明 inlinableParentFinalMethod4 以无类型保护的方式进行内联,但结果表明事实并非如此。

多态性



最后,我们来看涉及多态分派(polymorphic dispatch)的测试结果。单态调用的性能开销与之前virtual方法相近。但对于双态与超多状态调用,由于需要在一张较大的虚拟表上面进行查找,所以需要更多的时间。而一旦我们开启内联支持,类型分析(type profiling )将会在单态或双态的调用点启用,使得在这些调用点上的方法调用的开销减少。但与层级结构的实例一样,这只会减少少量的时间。相比而言,超多状态的实例则依旧耗时较长。记住,我并没有说在这个测试中hotspot禁用了内联,它只是没有实现多态调用点的多态内联缓存。

我们从中学到了什么?

我认为,需要我们引起注意的是,很多人没有认识到不同方式的方法调用所花费的时间是不一样的。即便有些人发现了这种问题,但他们不去证明是否真的如此。作为第一个吃螃蟹的人,我列出了各种坏的假设,因此我希望这份研究能够帮助到大家。以下是我很乐于与大家分享的一些结论:
  • 最快与最短的方法调用的类型之间存在巨大的性能差别。
  • 在实际应用中,添加或删除final关键字并不会真正影响性能。但如果除此以外,你还在层级结构上进行某些操作,那这些行为则可能导致性能下降。
  • 更深的类的层次结构并不会真正影响到调用的性能。
  • 单态调用比双态调用更快。
  • 双态调用比超多状态调用更快。
  • 我们在能够进行剖析(profile-ably),但是不能进行查验的单态调用点中看到类型保护,这种保护会使得这些调用点的调用性能低于那些能够进行查验的单态调用点。

我想说的是,对我而言,类型保护带来的性能开销是一个“重大发现”。这是一个我之前很少提及,并且总是当做无关事物忽视掉的因素。

注意事项与进一步工作

本文不能囊括这个话题的全部内容。因为:
  • 这篇博文所关注的影响到方法调用的性能的因素,只与类型有关。所以,有一个因素我并未提及:方法的长短或者说调用栈的深度——如果方法太长,那么它将不会被内联,为此你必须承受方法调用所带来的开销。另外,为了使代码具有易读性,你也应当把方法写得短小一些。
  • 在本次测试的所有我并没有尝试引入接口。如果你对此有兴趣的话,这里有一篇有关接口调用的性能的研究Mechanical Sympathy
  • 还有一个因素被我完全忽视了,那就是方法内联的优化方式在不同编译器上的效果差异。当编译器是仅关注某个方法(内部过程优化)时,它们需要足够地信息才能有效优化。内联的限制可以有效地减少其它优化所需要关注的范围。
  • 试着站在汇编语言的层面进行解释的话,会涉及更多的细节内容。

或许以上内容已经超出了本文的范畴,需要另写博文进行讨论。

原文链接:voxxed
译文链接:http://www.importnew.com/16202.html
  • 大小: 22.4 KB
  • 大小: 79 KB
  • 大小: 11 KB
  • 大小: 13 KB
  • 大小: 13.6 KB
  • 大小: 26.1 KB
  • 大小: 28.9 KB
  • 大小: 6.4 KB
来自: ImportNew
5
0
评论 共 2 条 请登录后发表评论
2 楼 雪飘寒 2015-06-29 11:40
dsjt 写道
身边的一件事:某程序员为了提升性能,把项目中字符串连接全部改成StringBuffer,后来又改成StringBuilder ,却对多层循环里的 sql 查询视而不见。


这个不能怪他技术不行,很多人的工作方法是我会啥就做啥,而不去思考根本原因,也可能是新手经验问题,性能应该是老程序员要考虑的......
1 楼 dsjt 2015-06-26 11:51
身边的一件事:某程序员为了提升性能,把项目中字符串连接全部改成StringBuffer,后来又改成StringBuilder ,却对多层循环里的 sql 查询视而不见。

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • .c和.h文件的区别

    编译器在编译时是以C文件为单位进行的,也就是说如果你的项目中一个C文件都没有,那么你的项目将无法编译,连接器是以目标文件为单位 ,它将一个或多个目标文件进行函数与变量的重定位,生成最终的可执行文件,在PC...

  • h文件和c文件的区别

    理论上来说C文件与头文件里的内容,只要是C语言所支持的,无论写什么都可以的,比如你在头文件中写函数体,只要在任何一个C文件包含此头文件就可以将这个函数编译成目标文件的一部分(编译是以C文件为单位的,如果不...

  • Java编写图像浏览器_浏览器下载图像(JAVA代码)

    03-07阅读8103简介: JavaWeb下载文件通常分为两种类型,一种是浏览器可以识别的文件类型,例如.txt,.excel,.zip等,可以通过使用以下链接来实现标签,但图片下载确实很麻烦. 使用链接时,浏览器无法识别图片的类型...

  • c++ .h和.c文件理解

    .c和.h文件的区别 一个简单的问题:.c和.h文件的区别 学了几个月的C语言,反而觉得越来越不懂了。同样是子程序,可以定义在.c文件中,也可以定义在.h文件中,那这两个文件到底在用法上有什么区别呢? 2楼: ...

  • cadence line 删除_cadence allegro16.3常见问题解答

    是什么地方需要设置,哪位大虾告诉哈我?答:setup/user preferences/display/display_nohilitefont 这个选项打勾就行了。2. 不小心按了Highlight Sov后部分线高亮成白色,怎样取消?答:这个是用来检查跨分割的,...

  • c和 h文件的区别

    c和 h文件的区别

  • C C语言中 *.c和*.h文件的区别!

    一个简单的问题:.c和.h文件的区别学了几个月的C语言,反而觉得越来越不懂了。同样是子程序,可以定义在.c文件中,也可以定义在.h文件中,那这两个文件到底在用法上有什么区别呢? 2楼:子程序不要定义在.h中。函数...

  • 本行没有输入值结余隐藏_EXCEL使用技巧及问题解答(函数&宏语句)-excel宏...

    也就是,在Excel中,A7单元,能否实现把后面的数字用算式来代替,如A(3+4),或者是单元格的嵌套,A(D12),恳请高手解答。解答:①=indirect("a"&d12)②我的想法:借一个单元格如B4用,键入="A"&D12...

  • .c和.h文件的区别(头文件与之实现文件的的关系~ )

    .c和.h文件的区别 一个简单的问题:.c和.h文件的区别 学了几个月的C语言,反而觉得越来越不懂了。同样是子程序,可以定义在.c文件中,也可以定义在.h文件中,那这两个文件到底在用法上有什么区别呢? 2楼: 子...

  • C语言中的头文件与原文件

    简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析阶段 3.编译阶段,首先编译成纯汇编语句,再将之汇编...

  • 读书笔记-详解C程序开发中 .c和.h文件的区别

    一个简单的问题:.c和.h文件的区别 学了几个月的C语言,反而觉得越来越不懂了。同样是子程序,可以定义在.c文件中,也可以定义在.h文件中,那这两个文件到底在用法上有什么区别呢? 2楼: 子程序不要定义在...

  • FastReport 模版打印如何实现

    FastReport 模版打印如何实现

  • 使用运算放大器模拟反相放大器的闭环电压增益simulink.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

  • 基于java的抗疫医疗用品销售平台设计与实现.docx

    基于java的抗疫医疗用品销售平台设计与实现.docx

  • vue+SpringBoot749乡村日常政务管理系统的设计与实现java毕业设计源码含论文.rar

    jdk版本:jdk1.8+ 前端:vue.js+ElementUI 开发工具:IDEA 或者eclipse都支持 编程语言: java 框架支持:springboot 数据库: mysql 版本不限 数据库工具:Navicat/SQLyog都可以 详细技术:java+springboot+vue+MYSQL+MAVEN 前端采用的Vue框架,后端采用java语言,sprinboot框架,mybatis操作数据源,使用软件:idea,eclipse、MySQL。完成了用户登录管理等模块的设计与实现。完成了系统数据库的设计,并基于MySQL数据库管理系统

  • java毕业设计源码ssm859基于SSM框架的线上DIY手工艺品综合平台+vue程序数据库含论文.rar

    前端采用的Vue框架,后端采用java语言,ssm框架,mybatis操作数据源,使用软件:idea,eclipse、MySQL。完成了用户登录管理等模块的设计与实现。完成了系统数据库的设计,并基于MySQL数据库管理系统 本系统基于SSM(Spring+SpringMVC+MyBatis)框架,适用于毕业设计, 基于B/S模式, mysql数据库,感兴趣的朋友们可以下载研究一下。 jdk版本:jdk1.8+ 前端:vue.js+ElementUI 开发工具:IDEA 或者eclipse都支持 编程语言: java 框架支持:ssm 数据库: mysql 版本不限 数据库工具:Navicat/SQLyog都可以 详细技术:java+ssm+vue+MYSQL+MAVEN

  • java基于ssm+vue企业在线培训系统源码 带毕业论文

    【资源说明】 1、开发环境:ssm框架;内含Mysql数据库;VUE技术 2、项目代码都经过严格调试,代码没有任何bug!下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料学习借鉴。 4、本资源作为“参考资料”如果需要实现其他功能,需要能看懂代码,并且热爱钻研,自行调试。

  • 任务管理系统Web(Spring+Mybaties).zip(毕设&课设&实训&大作业&竞赛&项目)

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

  • 这是一个基于mediapipe实现的运动计数项目;大三上期末小组作业;python。.zip

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

  • (全国物联网大赛一等奖,挑战杯铜奖)基于QT的人脸识别,定位导航,脑电心率测算,用GPRS传到服务端的疲劳驾驶检测系统(毕设&课

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

Global site tag (gtag.js) - Google Analytics