我想对 Java 应用程序进行一些计时测试。这就是我目前正在做的事情:
long startTime = System.currentTimeMillis();
doSomething();
long finishTime = System.currentTimeMillis();
System.out.println("That took: " + (finishTime - startTime) + " ms");
这样的性能测试有什么“错误”吗?有什么更好的方法?
我想对 Java 应用程序进行一些计时测试。这就是我目前正在做的事情:
long startTime = System.currentTimeMillis();
doSomething();
long finishTime = System.currentTimeMillis();
System.out.println("That took: " + (finishTime - startTime) + " ms");
这样的性能测试有什么“错误”吗?有什么更好的方法?
这种方法的一个缺陷是,doSomething()
执行所需的“实际”时间可能会因系统上正在运行的其他程序及其负载情况而有很大差异。这使得性能测量有些不精确。
假设代码是单线程的,跟踪执行代码所花费的时间的一种更准确的方法是查看调用期间线程消耗的 CPU 时间。您可以使用 JMX 类来做到这一点;特别是,与ThreadMXBean
。ThreadMXBean
您可以检索from的实例java.lang.management.ManagementFactory
,并且,如果您的平台支持它(大多数都支持),请使用该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!)
这种策略并不完美,但它较少受到系统负载变化的影响。
问题中显示的代码不是一个很好的性能测量代码:
编译器可能会选择通过重新排序语句来优化您的代码。是的,它可以做到这一点。这意味着您的整个测试可能会失败。它甚至可以选择内联被测方法并将测量语句重新排序到现在内联的代码中。
热点可能会选择重新排序您的语句、内联代码、缓存结果、延迟执行......
即使假设编译器/热点没有欺骗您,您测量的是“墙上时间”。您应该测量的是 CPU 时间(除非您使用操作系统资源并希望也包括这些资源,或者您在多线程环境中测量锁竞争)。
解决方案?使用真正的分析器。周围有很多,免费的分析器和演示/广告强度的时间锁定试验。
使用 Java Profiler 是最好的选择,它将为您提供对代码所需的所有洞察力。即响应时间、线程调用跟踪、内存利用率等
我会向你推荐 JENSOR,一个开源的 Java Profiler,因为它易于使用并且没有 CPU 开销。您可以下载它,检测代码,并获得您需要的有关代码的所有信息。
您可以从以下网址下载: http : //jensor.sourceforge.net/
请记住,System.currentTimeMillis()
不同操作系统的分辨率不同。我相信 Windows 大约是 15 毫秒。因此,如果您的doSomething()
运行速度快于时间分辨率,您将获得 0 的增量。您可以doSomething()
在循环中运行多次,但随后 JVM 可能会对其进行优化。
我想你也想在开始计时之前 doSomething() ,以便代码被 JITted 和“热身”。
这只是性能测试的一部分。根据您正在测试的东西,您可能需要查看堆大小、线程数、网络流量或一大堆其他东西。否则,我会将这种技术用于简单的事情,我只是想看看它们需要多长时间才能运行。
当您将一种实现与另一种实现进行比较或试图在代码中找到缓慢的部分时,这很好(尽管它可能很乏味)。这是一种非常好的技术,您可能会比其他任何技术都更频繁地使用它,但也要熟悉分析工具。
Japex可能对您有用,既可以作为一种快速创建基准的方法,也可以作为一种通过源代码研究 Java 中的基准问题的方法。