如何在Java中对方法执行时间进行基准测试?

56

我有一个自己用Java编写的程序,但是我想测试方法执行时间并获得特定方法的时间。我想知道是否可能通过Eclipse插件或插入一些代码来实现这一点。

我看到这是相当小的程序,只有不到1500行代码,最好使用专门的工具还是 System.currentTimeMillis()


4
在测量经过的时间时,应该使用System.nanoTime()而不是System.currentTimeMillis()(请参考https://dev59.com/J3VC5IYBdhLWcg3wnCf6#239661)。其他点(预热、JIT)由评论中提到的@Stephen仍然有效。 - Pascal Thivent
2
使用计时器类来完成此任务。 - jarvo69
10个回答

60

除了使用分析器之外,获取你想要的东西的一个简单方法是:

public class SomeClass{
   public void somePublicMethod()
   {
       long startTime = System.currentTimeMillis();
       someMethodWhichYouWantToProfile();
       long endTime = System.currentTimeMillis();
       System.out.println("Total execution time: " + (endTime-startTime) + "ms"); 
   }
 }

1
@Stephen C - 说得好。上述方法是我在尝试快速了解方法效率时主要使用的方法。在大多数情况下,我的要求并不需要精确到毫秒的精度。 - Chris Knight
1
我想补充一下这个答案,你可以考虑预热。通过使用-XX:CompileThreshold=100标志,当方法被执行100次时,你可以强制JVM将其编译为本地代码。因此,在不计时的情况下,你可以执行它100次。之后,它已经被编译成本地代码,你可以正确地测量它。 - Christophe De Troyer
有没有办法弥补调用方法所需的时间?例如,我不想计算复制参数和返回地址等所需的时间。 - Celeritas
4
这是用Java测量小代码段执行时间最糟糕的方法之一,因为(i)它没有预热代码,导致测量结果波动太大,无法有效地进行测量;(ii) 可能会受到DCE、CP和其他在真实代码中不存在的优化的影响;(iii)毫秒级别的粒度非常粗糙。请查看我的答案以获得更好的方法。 - El Marce
1
@ElMarce的答案在现代环境下显然更为优越。 - Visionary Software Solutions

33

如果瓶颈足够大,可以用分析器检测,使用分析器。

如果您需要更高的准确性,测量小段代码最好的方法是使用Java微基准测试框架,如OpenJDK的JMHGoogle的Caliper。我相信它们像JUnit一样容易使用,不仅可以获得更准确的结果,还能从社区中获得正确操作的专业知识。

以下是一个JMH微基准测试,用于测量Math.log()的执行时间:

private double x = Math.PI;

@Benchmark
public void myBenchmark() {
    return Math.log(x)
}

使用currentMillis()nanoTime()进行测量有许多限制:

  1. 它们具有延迟(它们也需要执行时间),这会影响您的测量结果。
  2. 它们的精度非常有限,这意味着您可以在Linux中测量26ns到26ns之间的事物,在Windows中可测量约300ns,如此处所述。
  3. 热身阶段未考虑在内,这使得您的测量结果变化很大。

Java中的currentMillis()nanoTime()方法可能很有用,但必须极其小心地使用,否则就会出现错误的测量结果,例如这样,其中测量的代码片段的顺序会影响测量结果,或者这样,作者错误地得出结论,即执行了数百万次操作,而实际上JVM没有执行任何操作并提升了代码,根本没有运行任何代码。

这是一段精彩的视频,讲解了如何正确地进行微基准测试:https://shipilev.net/#benchmarking


6

如果需要进行快速而简单的时间测量测试,请不要使用挂钟时间(System.currentTimeMillis())。建议改用System.nanoTime()

public static void main(String... ignored) throws InterruptedException {
    final long then = System.nanoTime();
    TimeUnit.SECONDS.sleep(1);
    final long millis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - then);
    System.out.println("Slept for (ms): " + millis); // = something around 1000.
}

3

您应该使用像

它们将轻松集成到任何IDE中,并显示您需要的任何详细信息。

当然,这些工具非常复杂,旨在用于分析复杂的程序。如果您只需要一些简单的基准测试,则建议您使用System.currentTimeMillis()System.nanoTime()并自行计算调用之间的毫秒差。


我明白了,这是一个非常小的程序,只有1500行不到,用专门的工具还是用System.currentTimeMillis()会更好呢? - KP65
如果你只需要检查某些方法的执行速度,可以使用__System.currentTimeMillis()__,但要注意它并不精确!你可能会遇到毫秒级别的量化和类似的问题。分析是更好的方法,但需要一点学习。 - Jack

3

使用分析器更好,因为您可以找出应用程序的平均执行时间和瓶颈。

我使用 VisualVM,它简单易用。


3

Google Guava有一个秒表,让事情变得简单易行。

Stopwatch stopwatch = Stopwatch.createStarted();
myFunctionCall();
LOGGER.debug("Time taken by myFunctionCall: " + stopwatch.stop());

1
这个有多准确? - Arefe

1

0
通常我会将时间存储在一个 .txt 文件中,以分析结果。
StopWatch sWatch = new StopWatch();
sWatch.start();

//do stuff that you want to measure
downloadContent();

sWatch.stop();

//make the time pretty
long timeInMilliseconds = sWatch.getTime();
long hours = TimeUnit.MILLISECONDS.toHours(timeInMilliseconds);
long minutes = TimeUnit.MILLISECONDS.toMinutes(timeInMilliseconds - TimeUnit.HOURS.toMillis(hours));
long seconds = TimeUnit.MILLISECONDS.toSeconds(timeInMilliseconds - TimeUnit.HOURS.toMillis(hours) - TimeUnit.MINUTES.toMillis(minutes));
long milliseconds = timeInMilliseconds - TimeUnit.HOURS.toMillis(hours) - TimeUnit.MINUTES.toMillis(minutes) - TimeUnit.SECONDS.toMillis(seconds);

String t = String.format("%02d:%02d:%02d:%d", hours, minutes, seconds, milliseconds);

//each line to store in a txt file, new line
String content = "Ref: " + ref + "  -  " + t + "\r\n";

//you may want wrap this section with a try catch
File file = new File("C:\\time_log.txt");
FileWriter fw = new FileWriter(file.getAbsoluteFile(), true); //append content set to true, so it does not overwrite existing data
BufferedWriter bw = new BufferedWriter(fw);
bw.write(content);
bw.close();

0

另一种定制解决方案可以基于以下帖子:http://www.mkyong.com/spring/spring-aop-examples-advice/

您还可以使用应用程序监视和SNMP周围的实用工具。如果您需要在生产环境中定期“计时”方法,则可能应考虑使用其中一个SNMP工具。


-2
你可以添加这段代码,它会告诉你方法执行所需的时间。
long millisecondsStart = System.currentTimeMillis();
executeMethod();
long timeSpentInMilliseconds = System.currentTimeMillis() - millisecondsStart;

13
三个问题:1)毫秒级时间可能会被量化,例如粒度为20毫秒。2)您实际上正在测量executeMethod()方法的时间加上System.currentTimeMillis()的时间。3)这并没有考虑JVM预热效应,例如JIT编译。 - Stephen C

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