Java性能测试

53

我想对一个Java应用程序进行一些定时测试。这是我目前正在做的事情:

long startTime = System.currentTimeMillis();
doSomething();
long finishTime = System.currentTimeMillis();
System.out.println("That took: " + (finishTime - startTime) + " ms");

这种性能测试有什么“问题”吗?有更好的方法吗?

重复问题: 使用Stopwatch进行基准测试是否可接受?


你是在问“秒表”基准测试是否可以,还是在问这样做是否正确? - Bill the Lizard
我真的希望两个问题都能得到回答...但更倾向于“以正确的方式进行,以获得准确的结果”。 - mainstringargs
离题并没有解决你的问题,所以我会把它放在评论中。Jon Bently的Google演讲“三个美丽的快速排序”是一个非常有趣的算法性能和分析的研究。很有意思。http://video.google.com/videoplay?docid=-1031789501179533828 - Todd
9个回答

36
那种方法的一个缺点是,执行doSomething()的“真实”时间可能会因系统上运行的其他程序和负载而大幅变化。这使得性能测量略显不精确。
单线程情况下,更准确地跟踪代码执行所需时间的一种方法是查看调用期间线程所消耗的CPU时间。您可以使用JMX类完成此操作,特别是ThreadMXBean。可以从java.lang.management.ManagementFactory获取ThreadMXBean的实例,并且如果平台支持它(大多数都支持),则可以使用getCurrentThreadCpuTime方法代替System.currentTimeMillis进行类似的测试。请注意,getCurrentThreadCpuTime以纳秒而非毫秒为单位报告时间。
以下是可用于执行测量的示例(Scala)方法:
def measureCpuTime(f: => Unit): java.time.Duration = {

  import java.lang.management.ManagementFactory.getThreadMXBean
  if (!getThreadMXBean.isThreadCpuTimeSupported)
    throw new UnsupportedOperationException(
      "JVM does not support measuring thread CPU-time")

  var finalCpuTime: Option[Long] = None
  val thread = new Thread {
    override def run(): Unit = {
      f
      finalCpuTime = Some(getThreadMXBean.getThreadCpuTime(
        Thread.currentThread.getId))
    }
  }
  thread.start()

  while (finalCpuTime.isEmpty && thread.isAlive) {
    Thread.sleep(100)
  }

  java.time.Duration.ofNanos(finalCpuTime.getOrElse {
    throw new Exception("Operation never returned, and the thread is dead " +
      "(perhaps an unhandled exception occurred)")
  })
}

(欢迎将上述内容翻译成Java!)

这种策略并不完美,但它对系统负载的变化不太敏感。


1
如果回答中有演示如何编写测试的示例代码,那么这个回答将会更好。 - Noctis Skytower
@NoctisSkytower,我加入了一些示例代码(执行测量的方法)。不幸(或幸运)的是,它是用Scala编写的;但你可以随意翻译和编辑答案。 - Chris W.
@Daan,链接已经失效了。 - Abhyudaya Sharma

16

该问题中展示的代码不是一个好的性能测量代码:

  1. 编译器可能会通过重新排序语句来优化您的代码。是的,它可以这样做。这意味着您的整个测试可能会失败。它甚至可以选择内联被测试的方法并将测量语句重新排序到现在内联的代码中。

  2. Hotspot 可能会重新排列你的语句,内联代码,缓存结果,延迟执行...

  3. 即使假设编译器/ hotspot 没有欺骗您,您测量的是“墙上时间”。您应该测量的是 CPU 时间(除非使用操作系统资源并希望将其包括在内,或者在多线程环境中测量锁争用)。

解决方案?使用真正的分析器。有很多选择,包括免费的分析器和商业强度的演示/限时试用版本。


3
Hotspot不仅会在某个时间点编译代码并进行你提到的优化,而且它实际上可以尝试多次编译,使用不同的优化方式:也就是说,时间重复可能意味着计时不同的代码!要非常非常小心…… - Neil Coffey
不想提供任何分析器选项吗? - John R Perry
1
由于名单经常变化,我喜欢的可能不适合您的需求,安全起见,建议自己搜索 Java Profilers 并找到适合您的工具。 个人喜欢使用YourKit进行大型性能分析,使用Java Microbenchmark Harness (JMH) 进行微基准测试。 - Ran Biron

4
使用Java Profiler是最好的选择,它将为您提供有关代码的所有洞察力,例如响应时间、线程调用跟踪、内存利用等。我建议您使用开源Java Profiler JENSOR,因为它易于使用且不会对CPU产生额外负担。您可以下载它,对代码进行检测,然后获取有关您的代码的所有信息。您可以从http://jensor.sourceforge.net/下载它。

2
请注意,System.currentTimeMillis() 的分辨率在不同的操作系统之间会有所不同。我相信 Windows 大约是 15 毫秒。因此,如果您的 doSomething() 运行速度比时间分辨率快,您将得到一个 delta 值为 0。您可以多次在循环中运行 doSomething(),但 JVM 可能会对其进行优化。

2

你有没有看过NetBeansEclipse中的性能分析工具?这些工具可以更好地帮助你了解代码中真正占用时间的部分。我通过使用这些工具发现了一些我之前没有意识到的问题。


0
我想你在开始计时之前会想要执行doSomething(),这样代码就可以被JIT编译并且“预热”了。

0

这只是性能测试的一部分。根据你要测试的东西,你可能需要查看堆大小、线程数、网络流量或其他许多因素。否则,对于我只想看看它们运行多长时间的简单事情,我会使用那种技术。


0

当你比较两个实现或者试图找到代码中的慢部分时,这是非常好的(尽管可能会很繁琐)。这是一种非常好的技巧,你可能会比使用其他任何技巧都更频繁地使用它,但也要熟悉性能分析工具。


0

Japex 可能对你有用,无论是作为快速创建基准测试的方法,还是通过源代码研究 Java 基准测试问题的方法。


1
该链接可能已经移动。Japex现在位于:https://japex.java.net/。 - charmoniumQ

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