Java 性能测试

2022-08-31 20:18:22

我想在Java应用程序上做一些时序测试。这就是我目前正在做的事情:

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

像这样的性能测试有什么“错误”吗?什么是更好的方法?

重复秒表基准测试可以接受吗?


答案 1

这种方法的一个缺陷是,执行所需的“真实”时间可能会有很大差异,具体取决于系统上运行的其他程序及其负载。这使得性能测量有些不精确。doSomething()

假设代码是单线程的,则跟踪执行代码所需时间的一种更准确的方法是查看线程在调用期间消耗的 CPU 时间。您可以使用JMX类执行此操作;特别是ThreadMXBean。您可以从 java.lang.management.ManagementFactory 检索 的实例,如果您的平台支持(大多数平台都支持),请使用 getCurrentThreadCpuTime 方法来代替 执行类似的测试。请记住,报告时间以纳秒为单位,而不是以毫秒为单位。ThreadMXBeanSystem.currentTimeMillisgetCurrentThreadCpuTime

下面是一个可用于执行测量的示例(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!

这种策略并不完美,但它受系统负载变化的影响较小。


答案 2

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

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

  2. 热点可能会选择对语句、内联代码、缓存结果、延迟执行等进行重新排序...

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

解决方案是什么?使用真正的探查器。周围有很多,包括免费的分析器和广告强度的演示/时间锁定试用。


推荐