首页 / 软件开发 / JAVA / Java 6中的线程优化真的有效么?——第二部分
Java 6中的线程优化真的有效么?——第二部分2011-05-20 infoq.com Jeroen Borgers 译:韩锴在本文的第一部分中,我们通过一个单一线程的基准,比较了同步的 StringBuffer和非同步的StringBuilder之间的性能。从最初的基准测试结果来看 ,偏向锁提供了最佳的性能,比其他的优化方式更有效。测试的结果似乎表明获 取锁是一项昂贵的操作。但是在得出最终的结论之前,我决定先对结果进行检验 :我请我的同事们在他们的机器上运行了这个测试。尽管大多数结果都证实了我 的测试结果,但是有一些结果却完全不同。在本文的第二部分中,我们将更深入 地看一看用于检验测试结果的技术。最后我们将回答现实中的问题:为什么在不 同的处理器上的锁开销差异如此巨大?基准测试中的陷阱通过一个基准测试,尤其是一个“小规模基准测试”(microbenchmark),来 回答这个问题是非常困难的。多半情况下,基准测试会出现一些与你期望测量的 完全不同的情景。即使当你要测量影响这个问题的因素时,结果也会被其他的因 素所影响。有一点在这个实验开始之初就已经很明确了,即这个基准测试需要由 其他人全面地进行审查,这样我才能避免落入报告无效基准测试数据的陷阱中。 除了其他人的检查以外,我还使用了一些工具和技术来校验结果,这些我会在下 面的几节中谈到。结果的统计处理大多数计算机所执行的操作都会在某一固定的时间内完成。就我的经验而言, 我发现即使是那些不确定性的操作,在大多数条件下基本上也能在固定的时间内 完成。正是根据计算的这种特性,我们可以使用一种工具,它通过测量让我们了 解事情何时开始变得不正常了。这样的工具是基于统计的,其测量结果会有些出 入。这就是说,即使看到了一些超过正常水平的报告值,我也不会做过多过的解 释的。原因是这样的,如果我提供了指令数固定的CPU,而它并没有在相对固定的 时间内完成的话,就说明我的测量受到了一些外部因素的影响。如果测试结果出 现了很大的异常,则意味着我必须找到这个外部的影响进而解决它。尽管这些异常效果会在小规模基准测试中被放大,但它不至于会影响大规模的 基准测试。对于大规模的基准测试来说,被测量的目标应用程序的各个方面会彼 此产生干扰,这会带来一些异常。但是异常仍然能够提供一些很有益的信息,可 以帮助我们对干扰级别作出判断。在稳定的负荷下,我并不会对个别异常情况感 到意外;当然,异常情况不能过多。对于那些比通常结果大一些或小一些的结果 ,我会观察测试的运行情况,并将它视为一种信号:我的基准测试尚未恰当地隔 离或者设置好。这样对相同的测试进行不同的处理,恰恰说明了全面的基准测试 与小规模基准测试之间的不同。最后一点,到此为止仍然不能说明你所测试的就是你所想的。这至多只能说明 ,对于最终的问题,这个测试是最有可能是正确的。预热方法的缓存JIT会编译你的代码,这也是众多影响基准测试的行为之一。Hotspot会频繁地 检查你的程序,寻找可以应用某些优化的机会。当找到机会后,它会要求 JIT编 译器重新编译问题中的某段代码。此时它会应用一项技术,即当前栈替换(On Stack Replacement,OSR),从而切换到新代码的执行上。执行OSR时会对测试产 生各种连锁影响,包括要暂停线程的执行。当然,所有这样的活动都会干扰到我 们的基准测试。这类干扰会使测试出现偏差。我们手头上有两款工具,可以帮助 我们标明代码何时受到JIT的影响了。第一个当然是测试中出现的差异,第二个是 -XX:-PrintCompilation标记。幸运的是,如果不是所有的代码在测试的早期就进 行JIT化处理,那么我们可以将它视为另外一种启动时的异常现象。我们需要做的 就是在开始测量前,先不断地运行基准测试,直到所有代码都已经完成了JIT化。 这个预热的阶段通常被称为“预热方法的缓存 ”。大多数JVM会同时运行在解释的与本机的模式中。这就是所谓的混合模式执行 。随着时间的流逝,Hotspot和JIT会根据收集的信息将解释型代码转化为本机代 码。Hotspot为了决定应该使用哪种优化方案,它会抽样一些调用和分支。一旦某 个方法达到了特定的阈值后,它会通知JIT生成本机代码。这个阈值可以通过- XX:CompileThreshold标记来设定。例如,设定-XX:CompileThreshold=10000, Hotspot会在代码被执行10,000次后将它编译为本机代码。堆管理下一个需要考虑的是垃圾收集,或者更广为人知的名字—堆管理。在任何应用 程序执行的过程中,都会定期地发生很多种内存管理活动。它们包括:重新划分 栈空间大小、回收不再被使用的内存、将数据从一处移到另一处等等。所有这些 行为都导致JVM影响你的应用程序。我们面对的问题是:基准测试中是否需要将内 存维护或者垃圾回收的时间包括进来?问题的答案取决于你要解决的问题的种类 。在本例中,我只对获取锁的开销感兴趣,也就是说,我必须确保测试中不能包 含垃圾回收的时间。这一次,我们又能够通过异常的现象来发现影响测试的因素 ,一旦出现这种问题,垃圾回收都是一个可能的怀疑对象。明确问题的最佳方式 是使用 -verbose:gc标志,开启GC的日志功能。