我想对 Java 应用程序进行一些计时测试。这就是我目前正在做的事情:
long startTime = System.currentTimeMillis();
doSomething();
long finishTime = System.currentTimeMillis();
System.out.println("That took: " + (finishTime - startTime) + " ms");
这样的性能测试有什么“问题”吗?有什么更好的办法吗?
重复:秒表基准测试可以接受吗?
该方法的一个缺陷是,执行所需的“实时”时间
doSomething()
可能会有很大差异,具体取决于系统上运行的其他程序及其负载。这使得性能测量有些不精确。
跟踪执行代码所需时间的一种更准确的方法(假设代码是单线程的)是查看线程在调用期间消耗的 CPU 时间。您可以使用 JMX 类来做到这一点;特别是,与
ThreadMXBean
。您可以从 ThreadMXBean
检索
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() ,以便代码被即时编译并“预热”。
Japex 可能对您有用,既可以作为快速创建基准的方法,也可以作为通过源代码研究 Java 基准测试问题的方法。