大方法的执行性能与调优过程小记

你写过超过2500行的方法么?通常来说,这么大的方法并不多见,一般都是使用机器辅助生成的为主,这种情况在模板编译或其它语言的自动转换中比较常见。例如,对一个复杂的JSP页面,机器有可能会为它生成一个复杂的servlet方法去实现。

然而在Hotspot上运行这种大方法,很可能会有性能问题。例如,把文章所附DEMO的play()方法的内容分别重复拷贝1、2、4、8、16、32次并依次运行,在我的机器(Hotspot_1.6u22/Windows)上得到的play()的执行消耗时间分别是28.43、54.72、106.28、214.41、419.30、1476.40毫秒/万次。在重复拷贝1~16次时,随着代码量增加,方法执行所消耗的时间也对应成倍增加。当重复拷贝32次时,方法却多消耗了80%的时间。如果把这个play()方法拆分成play1()和play2(),让它们的方法体都是16次的重复拷贝,play1()最后再调用play2(),那么,play1()+play2()的执行消耗时间是857.75毫秒/万次,恰好是之前重复拷贝16次所消耗的时间的两倍。为什么同样功能的一段代码放在一个方法中执行会变慢,拆分成两个方法就变快?

大家知道,JVM一开始是以解释方式执行字节码的。当这段代码被执行的次数足够多以后,它会被动态优化并编译成机器码执行,执行速度会大大加快,这就是所谓的JIT编译。DEMO的play()方法在被统计消耗时间之前,已经预热执行了2000次,满足ClientVM的方法JIT编译阈值CompileThreshold=1500次的要求,那么,它是不是真的被JIT编译了呢?我们可以增加VM参数”-XX:+PrintCompilation”调查一下。在+PrintCompilation打开以后,列出了JVM在运行时进行过JIT编译的方法。下面是经过32次重复拷贝的play()方法的JIT编译记录(只列出需要关心的部分):


而分成两部分的play1()+plaay2()的JIT编译记录则为:


显然,经过重复拷贝32次的play()方法没有经过JIT编译,始终采用解释方式执行,而分拆开的play1()+play2()经过JIT编译,所以难怪play()要慢80%。

为什么play()方法不受JVM青睐呢,是太长了么?这只能到Hotspot源码中去翻答案了。在compilationPolicy.cpp中有写道:


当DontCompileHugeMethods=true且代码长度大于HugeMethodLimit时,方法不会被编译。DontCompileHugeMethods与HugeMethodLimit的值在globals.hpp中定义:


上面两个参数说明了Hotspot对字节码超过8000字节的大方法有JIT编译限制,这就是play()杯具的原因。由于使用的是product mode的JRE,我们只能尝试关闭DontCompileHugeMethods,即增加VM参数”-XX:-DontCompileHugeMethods”来强迫JVM编译play()。再次对play()进行测试,耗时855毫秒/万次,性能终于上来了,输出的JIT编译记录也增加了一行:


使用”-XX:-DontCompileHugeMethods”解除大方法的编译限制,一个比较明显的缺点是JVM会尝试编译所遇到的所有大方法,者会使JIT编译任务负担更重,而且需要占用更多的Code Cache区域去保存编译后的代码。但是优点是编译后可以让大方法的执行速度变快,且可能提高GC速度。运行时Code Cache的使用量可以通过JMX或者JConsole获得,Code Cache的大小在globals.hpp中定义:


一旦Code Cache满了,HotSpot会停止所有后续的编译任务,虽然已编译的代码不受影响,但是后面的所有方法都会强制停留在纯解释模式。因此,如非必要,应该尽量避免生成大方法;如果解除了大方法的编译限制,则要留意配置Code Cache区的大小,准备更多空间存放编译后的代码。

最后附上DEMO代码:

[java]
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

public class HugeMethodDemo {

public static void main(String[] args) throws Exception {
HugeMethodDemo demo = new HugeMethodDemo();

int warmup = 2000;
demo.run(warmup);

int loop = 200000;
double total = demo.run(loop);
double avg = total / loop / 1e6 * 1e4;

System.out.println(String.format(
"Loop=%d次, " + "avg=%.2f毫秒/万次", loop, avg));
}

private long run(int loop) throws Exception {
long total = 0L;

for (int i = 0; i < loop; i++) {
Map theWorld = buildTheWorld();
StringWriter console = new StringWriter();

}

return total;
}

private Map buildTheWorld() {
Map context = new HashMap();
context.put("name", "D&D");
context.put("version", "1.0");

Map game = new HashMap();
context.put("game", game);

Map player = new HashMap();
game.put("player", player);
player.put("level", "26");
player.put("name", "jifeng");
player.put("job", "paladin");
player.put("address", "heaven");
player.put("weapon", "sword");
player.put("hp", 150);

String[] bag = new String[] { "world_map", "dagger",
"magic_1", "potion_1", "postion_2", "key" };
player.put("bag", bag);
return context;
}

private void play(Map theWorld, Writer console) throws Exception {
// 重复拷贝的开始位置
if (true) {
String name = String.valueOf(theWorld.get("name"));
String version = String.valueOf(theWorld.get("version"));
console.append("Game ").append(name).append(" (v").append(version).append(")n");
Map game = (Map) theWorld.get("game");
if (game != null) {
Map player = (Map) game.get("player");
if (player != null) {
String level = String.valueOf(player.get("level"));
String job = String.valueOf(player.get("job"));
String address = String.valueOf(player.get("address"));
String weapon = String.valueOf(player.get("weapon"));
String hp = String.valueOf(player.get("hp"));
console.append(" You are a ").append(level).append(" level ").append(job)
.append(" from ").append(address).append(". n");
console.append(" Currently you have a ").append(weapon).append(" in hand, ")
.append("your hp: ").append(hp).append(". n");
console.append(" Here are items in your bag: n");
for (String item : (String[]) player.get("bag")) {
console.append(" * ").append(item).append("n");
}
} else {
console.append("tPlayer not login.n");
}
} else {
console.append("tGame not start yet.n");
}
}
// 重复拷贝的结束位置
}
}
[/java]

本文来源于"阿里中间件团队播客",原文发表时间"2010-12-05"

时间: 2024-10-25 03:00:56

大方法的执行性能与调优过程小记的相关文章

成为MySQL DBA博客-性能配置调优

首先 性能优化是一个持续的过程,安装MySQL通常是调整操作系统和数据库配置的第一步.而数据库是一个动态系统,这是一个永无止境的故事.你的MySQL数据库起初可能是CPU绑定的,因为你有足够的内存和很少的数据.随着时间的推移,它可能会改变,磁盘访问可能会变得更加频繁.正如你可以想象的那样,I / O是主要关心的服务器的配置看起来不同于所有数据都适合内存的服务器的配置.此外,您的查询组合也可能随时间而改变,因此访问模式或MySQL中可用功能的使用(如自适应哈希索引)可随之改变. OS系统优化 需要

(活动)MySQL DBA之路 | 性能配置调优篇

一.简介 数据库服务器需要CPU.内存. 磁盘和网络才能运行,了解这些资源对于DBA来说非常重要,因为任何的超载行为都可能成为限制因素,导致数据库服务器性能不佳.DBA的主要任务就是调整系统和数据库的配置,避免可用资源的过渡利用和利用不足. 首先,性能优化是一个持续的过程,安装MySQL通常是调整操作系统和数据库配置的第一步.而数据库是一个动态系统,这是一个永无止境的故事.你的MySQL数据库起初可能是CPU绑定的,因为你有足够的内存和很少的数据.随着时间地推移,它可能会改变,磁盘访问可能会变得

介绍IBM WebSphere Commerce性能调优的基本原则和方法

如果需要深入分析复杂问题,可以借助 IBM 提供的性能分析工具做进一步的研究. 参数优化建议 WebSphere http://www.aliyun.com/zixun/aggregation/3914.html">Commerce 是基于 WebSphere 应用程序服务器开发的大型电子商务应用程序.在初次成功安装 WebSphere Commerce 应用程序之后,安装程序已经对服务器上的关键参数进行了初始化调整.这组默认值是 WebSphere Commerce 测试团队经过反复测试

通向架构师的道路(第三天)之apache性能调优

一.总结前一天的学习 在前两天的学习中我们知道.了解并掌握了Web Server结合App Server实现单向Https的这样的一个架构.这个架构是一个非常基础的J2ee工程上线布署时的一种架构.在前两天的教程中,还讲述了Http服务器.App Server的最基本安全配置(包括单向https的实现), 它只是避免了用户可以通过浏览器侵入我们的Web访问器或者能够通过Web浏览器来查询我们的Web目录结构及其目录内的文件与相关内容,这种入侵我们把它称为: Directory traversal

ANDROID性能调优

http://www.trinea.cn/android/android-performance-demo/#comment-115 本文主要分享自己在appstore项目中的性能调优点,包括同步改异步.缓存.Layout优化.数据库优化.算法优化.延迟执行等.   性能优化专题已完成五部分: 性能优化总纲--性能问题及性能调优方式性能优化第三篇--Java(Android)代码优化性能优化第二篇--布局优化性能优化第一篇--数据库性能优化 性能优化实例    一.性能瓶颈点 整个页面主要由6个

对话马丁·福勒(Martin Fowler)——第六部分:性能与过程调优

第一部分:重构第二部分:设计原则与代码所有权第三部分:进化型设计第四部分:灵活性与复杂性第五部分:测试驱动开发第六部分:性能与过程调优 可维护性与效率 比尔:我在丹佛机场的红地毯俱乐部(Red Carpet Club)[1]中常常碰到名人.今年夏天我碰到了 Calista Flockhart (卡莉斯塔·弗洛克哈特)[2], 而去年我碰到了你.我是个追星族,但是由于害怕哈里森·福特,没敢跟 Calista 搭讪.不过,你和我倒是坐下来喝了杯啤酒.记得当时你曾对我说过,应该以程序员能读懂的字符格式

&lt;Linux性能调优指南&gt;主要思路流程

网上IBM很早放出的一本免费电子书, 十来年了,参考意义还是很大. 国内有翻译成中文在线阅读的版本. 见如下两个URL Linux Performance and Tuning Guidelines <Linux性能调优指南> https://www.gitbook.com/book/lihz1990/transoflptg/details ========================================= 服务器优化思路 管理变更流程 管理变更和性能优化并不直接相关,但可能是

一种正规的性能调优方法──基于等待的调优

企业java应用的性能调优是一项艰巨的.有时甚至是徒劳的任务,这是由现代 应用的复杂性和缺少正规的调优方法导致的.现代企业应用与十年前的应用相比 差距很大,如今这些应用支持多输入.多输出.复杂的框架和业务处理引擎.而 十年之前,基于web的企业应用只是通过网络浏览器获得输入信息,然后与数据库 或者遗留系统交互进行后台处理,最后把输出结果返回给浏览器(HTML).现在 ,输入信息可以来自HTML浏览器.富客户端.移动设备或者网络服务,它可以跨 越运行在不同架构下的servlets或者门户容器,这反

HBase性能调优

因官方Book Performance Tuning部分章节没有按配置项进行索引,不能达到快速查阅的效果.所以我以配置项驱动,重新整理了原文,并补充一些自己的理解,如有错误,欢迎指正. 配置优化 zookeeper.session.timeout默认值:3分钟(180000ms)说明:RegionServer与Zookeeper间的连接超时时间.当超时时间到后,ReigonServer会被Zookeeper从RS集群清单中移除,HMaster收到移除通知后,会对这台server负责的region