-XX:+PrintCompilation
、-verbose:gc
等参数运行,以便您可以验证编译器和JVM的其他部分在计时阶段期间是否执行了意外的工作。
规则 2.1:在计时和热身阶段的开头和结尾打印消息,以便您可以验证在计时阶段没有来自规则 2 的输出。
规则 3:注意 -client
和 -server
之间的区别,以及 OSR 和常规编译。带有 -XX:+PrintCompilation
标志的报告会用@符号表示非初始入口点的 OSR 编译,例如:Trouble$1::run @ 2 (41 bytes)
。如果追求最佳性能,请优先选择服务器端而不是客户端,并选择常规编译而不是 OSR 编译。
规则 4:注意初始化效果。在计时阶段期间不要进行首次打印,因为打印会加载和初始化类。除非您专门测试类加载(在这种情况下仅加载测试类),否则不要在热身阶段之外加载新类(或最终报告阶段)。规则 2 是针对此类效果的第一道防线。
规则5:注意去优化和重新编译效应。在计时阶段中,不要首次使用任何代码路径,因为编译器可能根据早先的乐观假设,认为该路径根本不会被使用而将其丢弃和重新编译。第2条规则是对抗此类效应的第一道防线。
规则6:使用适当的工具来读取编译器的心思,并且预料到它生成的代码会让你感到惊讶。在形成关于某些东西是更快还是更慢的理论之前,请自行检查代码。
规则7:减少测量中的噪音。在一个安静的机器上运行基准测试,并运行多次,舍弃异常值。使用-Xbatch
命令将编译器与应用程序串行化,并考虑设置-XX:CICompilerCount=1
以防止编译器与自身并行运行。尽力减少GC开销,将Xmx
(足够大)设置为等于Xms
,如果可用,可以使用UseEpsilonGC
。
System.nanoTime()
并不能保证比System.currentTimeMillis()
更准确。它只能保证至少与后者一样准确。但通常情况下,它确实比后者更准确。 - GravitySystem.nanoTime()
而非 System.currentTimeMillis()
的主要原因在于前者保证单调递增。减去两个 currentTimeMillis
调用的返回值可能会导致负数结果,这可能是因为系统时间被某个 NTP 守护程序调整所致。 - Waldheinz我知道这个问题已经被标记为已回答,但我想提到两个库,这些库可以帮助我们编写微基准测试。
入门教程
入门教程
System.gc()
,但最好在测试之间运行它,以便每个测试都有一个“干净”的内存空间可以使用。 (是的, gc()
更像是一个提示而不是保证,但根据我的经验,它非常有可能真正进行垃圾收集。)我正在撰写一篇关于.NET基准测试框架设计的博客文章。 我有couple篇earlier posts可能能够给您一些想法-当然并不是所有内容都适用,但其中的一些可能会有所帮助。
gc
总是会释放未使用的内存。 - Sanjay T. SharmaSystem.gc()
,那么你如何建议在一个测试中最小化由前面的测试创建的对象所导致的垃圾回收?我是实用主义者,而非教条主义者。 - Jon Skeet基准测试应该衡量时间/迭代次数还是迭代次数/时间,为什么?
这取决于你想要测试的内容。
如果你关心延迟,那么使用时间/迭代次数,如果你关心吞吐量,那么使用迭代次数/时间。
确保您以某种方式使用在基准测试代码中计算的结果。否则,您的代码可能会被优化掉。
如果你正在尝试比较两个算法,请对每个算法进行至少两次基准测试,并交替顺序。例如:
for(i=1..n)
alg1();
for(i=1..n)
alg2();
for(i=1..n)
alg2();
for(i=1..n)
alg1();
我发现同一算法在不同的执行中有明显的差异(有时候可以达到5-10%)。
同时,确保n非常大,以便每个循环的运行时间至少为10秒左右。迭代次数越多,基准时间中的有效数字就越多,数据也更可靠。
在Java编写微基准测试时有许多可能的陷阱。
第一:您必须计算各种需要随机时间的事件:垃圾回收,缓存效应(对于文件的操作系统和对于内存的CPU),IO等等。
第二:您不能信任非常短时间间隔的测量时间的准确性。
第三:JVM在执行时会优化您的代码。因此,同一JVM实例中的不同运行将变得越来越快。
我的建议:使您的基准测试运行几秒钟,这比毫秒级别的运行更可靠。预热JVM(即至少运行一次基准测试而不进行测量,以使JVM可以运行优化)。多次运行您的基准测试(可能5次),并取中位数值。在新的JVM实例中运行每个微基准测试(为每个基准测试调用新的Java),否则JVM的优化效果会影响后续运行的测试。不要执行在预热阶段未执行的事情(因为这可能触发类加载和重新编译)。
需要注意的是,在比较不同实现时,分析微基准测试结果可能也很重要。因此,应该进行显著性测试。
这是因为实现 A 在大部分基准测试运行中可能比实现 B 更快。但是,A 的差异性可能更高,因此与 B 相比,A 的测量性能优势将毫无意义。
因此,编写和正确运行微基准测试同样重要,正确分析测试结果也非常关键。
long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");
问题在于代码完成时你并没有立即获得结束时间。可以尝试以下方法:
final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");
println
的调用,没有单独的标题行或其他东西,并且System.nanoTime()
必须作为构造该调用的字符串参数的第一步进行评估。编译器在第一个示例中所能做的事情,在第二个示例中也同样可以做到,而且两者都没有鼓励它们在记录停止时间之前做额外的工作。 - Peter Cordes