Brent Boyer 是 Elliptic Group 的一名程序员,他在 IBM 开发者网站上发表了一篇名为“健壮的Java 基准(Robust Java Benchmarking)”的文章。这篇文章包含两个部分,主要探究了如何实现有效的Java 基准。首先,他论述了当前编译器下不同JVM 有着不同的特性和优化,而这些特性或是优化有可能会对性能测试产生负面影响。举例来说,假如有一段复杂的代码段,最后计算得到的是一个从未使用的值,那么强势编译器就会对这段代码进行优化,基准则会忽略这段计算。为了说明这一点,他在他哦个一台计算机上连续运行了很多次相同的代码段,结果运行时都是4.9 秒,但当他删除了打印结果的println 语句之后,运行时间则缩短到0.08 秒。他还指出,时间度量的粒度在不同的操作系统下是不一样的,因此在基准测试的时候,一定要弄清楚当前系统的时间度量粒度。他说,和System.nanoTime() 相比,System.currentTimeMillis() 就不是一个度量运行时间的好方法(),因为它在Windows XP 上只有15ms 的精度(但在具有2.6 内核的Linux 上却可以达到1ms 的精度)。
在阐述了这些特别的行为之后,Boyer 提到了一些在做典型的基准测试时容易忽略的一些问题,比如JVM 缓存、资源回收(如垃圾收集、对象清理)。他认为避免这些问题的唯一有效方式是“预热(warm up)”代码直到代码达到一个稳定态。“预热”过程很耗时间并且很具挑战,因为有些JVM 在其触发编译之前可能已经将一个函数执行了10,000 次(但在编译触发前,代码还处于解析状态)。代码达到稳定状态之后,基准必须对这段代码运行多次,然后才能对结果做出有效的统计分析。
此外,Boyer 还建议采用基准框架来做基准测试,他本人就编写了这样一个框架。该框架能够展示以不同数目的元素来访问数据结构(原生数组、 ArrayLists、Vectors、HashMap、TreeMap 等等)中的数据的差异。Boyer 向大家展示了两个有趣的分析结果:(1)即使运行时短暂到以纳秒计数,其基准框架仍能计算出平均访问时间。(2)不同负载下,某些数据结构的反应令人非常吃惊。其中一个特别的例子是 ConcurrentHashMaps 与TreeMap 的比较:在同样拥有1024 个元素时,CurrentHashMaps 的表现要远远好于 TreeMap,但当元素数量上升到1024x1024 的时候,两者表现就相差不大。这很出乎意料,因为hash map 的搜索时间是常量,但trees 的搜索时间却是log(n)。除却这些令人吃惊的奇怪的结果,这篇文章还是非常值得一读的,尤其是在对Java 代码进行基准评测时,Boyer 提出的建议还是值得参考的。
评论