使用Java创建快速/可靠的基准测试?

5

我正在尝试使用Java创建基准测试。目前我有以下简单的方法:

public static long runTest(int times){
    long start = System.nanoTime();     
    String str = "str";
    for(int i=0; i<times; i++){
        str = "str"+i;
    }       
    return System.nanoTime()-start;     
}

我目前在另一个循环中多次运行此循环,该循环发生多次,并获得运行此方法所需的最小/最大/平均时间。然后我在另一个线程上启动一些活动并再次进行测试。基本上,我只想获得一致的结果...如果我运行runTest循环1000万次,结果似乎相当一致:

Number of times ran: 5
The max time was: 1231419504 (102.85% of the average)
The min time was: 1177508466 (98.35% of the average)
The average time was: 1197291937
The difference between the max and min is: 4.58%

Activated thread activity.

Number of times ran: 5
The max time was: 3872724739 (100.82% of the average)
The min time was: 3804827995 (99.05% of the average)
The average time was: 3841216849
The difference between the max and min is: 1.78%

Running with thread activity took 320.83% as much time as running without.

但是这似乎有点过分,需要花费一些时间... 如果我在runTest循环中尝试一个更低的数字(100000),它开始变得非常不一致:

    Number of times ran: 5
    The max time was: 34726168 (143.01% of the average)
    The min time was: 20889055 (86.02% of the average)
    The average time was: 24283026
    The difference between the max and min is: 66.24%

    Activated thread activity.

    Number of times ran: 5
    The max time was: 143950627 (148.83% of the average)
    The min time was: 64780554 (66.98% of the average)
    The average time was: 96719589
    The difference between the max and min is: 122.21%

    Running with thread activity took 398.3% as much time as running without.

有没有一种方法可以进行如此一致且高效/快速的基准测试?

顺便说一下,我并不是在测试开始和结束时间之间的代码。我正在以某种方式测试CPU负载(看看我如何启动一些线程活动并重新测试)。因此,我认为我正在寻找的是一种替代我在“runTest”中拥有的代码的东西,它将产生更快和更一致的结果。

谢谢


这可能是JIT编译热路径的原因...?也许尝试禁用JIT(Sun VM上有一个选项可以这样做)? - Rom1
作为一条注释:一个很棒的库,可以提供上述所有基准信息以及更多信息,它就是JAMon。你不必自己实现所有这些。 - Benjamin Muschko
@Rom1,我不确定你的意思。 @Benjamin,从我的判断来看,那更多是关于基准测试代码方面的。 - Smern
3个回答

5
简而言之:
微基准测试非常复杂,因此使用像Benchmarking框架这样的工具http://www.ellipticgroup.com/misc/projectLibrary.zip - 并且对结果持怀疑态度(“在微基准测试中放置微信任”,Cliff Click博士)。
详细说明:
有很多因素可以强烈影响结果:
- System.nanoTime的精度和准确性:在最坏的情况下,它与System.currentTimeMillis一样糟糕。 - 代码预热和类加载 - 混合模式:JVM JIT编译器(参见Edwin Buck的答案)仅在调用代码块足够频繁(1500或1000次)后才进行编译。 - 动态优化:去优化、栈替换、死代码消除(您应该使用在循环中计算的结果,例如打印它) - 资源回收:垃圾回收(请参见Michael Borgwardt的答案)和对象终结 - 缓存:I/O和CPU - 整个操作系统:屏幕保护程序、电源管理、其他进程(索引器、病毒扫描等)
Brent Boyer的文章“Robust Java benchmarking, Part 1: Issues”(http://www.ibm.com/developerworks/java/library/j-benchmark1/index.html)是所有这些问题的良好描述以及您是否/可以对抗它们(例如使用JVM选项或事先调用ProcessIdleTask)。
您将无法消除所有这些因素,因此进行统计是个好主意。但是:
- 您应该花费精力计算标准差而不是计算最大值和最小值之间的差异(结果{1、1000次2、3}与{501次1、501次3}不同)。 - 可靠性通过生成置信区间(例如通过引导)来考虑。
上述Benchmark框架(http://www.ellipticgroup.com/misc/projectLibrary.zip)使用这些技术。您可以在Brent Boyer的文章“Robust Java benchmarking, Part 2: Statistics and solutions”(https://www.ibm.com/developerworks/java/library/j-benchmark2/)中了解它们。

1

你的代码最终主要测试垃圾回收性能,因为在循环中将字符串附加到末尾会创建并立即丢弃大量越来越大的字符串对象。

这是一种固有的导致测量结果变化巨大的情况,并受多线程活动的影响。

我建议你在循环中做一些其他具有更可预测性能的事情,比如数学计算。


是的,我希望用更一致的东西来替换它。我尝试循环 temp * i%10000(其中 temp 是初始化为 10000 的整数),但我仍然得到非常不一致的结果。 - Smern

0
在1000万次运行中,HotSpot编译器很可能会检测到一个“频繁使用”的代码片段,并将其编译成本地机器代码。
JVM字节码是解释的,这使得它更容易受到JVM中发生的其他后台进程(如垃圾回收)的干扰。
一般来说,这些基准测试充斥着不成立的假设。如果没有大量证据证明初始测量(时间)实际上并没有测量您的任务,而可能是一些其他后台任务,则不能相信微基准测试真正证明了它想要证明的内容。如果您不尝试控制后台任务,则该测量的用处要小得多。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接