如何在Java中计算方法的执行时间?

1018
  1. 如何获取方法的执行时间?
  2. 是否有一个Timer实用程序类来计算任务所需的时间等?

大多数在Google上搜索的结果都是关于调度线程和任务的定时器,这不是我想要的。


JAMon API是一个免费、简单、高性能、线程安全的Java API,允许开发人员轻松监控生产应用程序的性能和可扩展性。JAMon跟踪点击次数、执行时间(总计、平均值、最小值、最大值、标准偏差)等等。http://jamonapi.sourceforge.net/下载: http://sourceforge.net/project/showfiles.php?group_id=96550 - Mike Pone
2
你可能还想看看Apache Commons Lang StopWatch类。一个简单但实用的工具类。 - user101561
类似的问题:如何在Java中编写正确的微基准测试? - Basil Bourque
是的,StopWatch非常适合这个。 - Shubham Pandey
Java 8使用Instant类:https://dev59.com/InVC5IYBdhLWcg3wz0h9#30975902 - akhil_mittal
42个回答

1454

总有一种老派的方式:

long startTime = System.nanoTime();
methodToTime();
long endTime = System.nanoTime();

long duration = (endTime - startTime);  //divide by 1000000 to get milliseconds.

274
实际上,它是“新式”的,因为您使用了nanoTime,这个函数直到Java 5才被加入。 - John Gardner
11
在Java中,这似乎是通常的方法,或者使用System.currentTimeMillis()。至少在我看来是这样的。仍然让我有点惊讶的是,没有像Timer t = new Timer(); String s = t.getElapsed(format); 这样漂亮的内置类。 - Ogre Psalm33
18
nanoTime不能保证比currentTimeMillis()更精确,尽管通常如此。详情请参见http://forums.sun.com/thread.jspa?messageID=9460663和http://www.simongbrown.com/blog/2007/08/20/millisecond_accuracy_in_java.html。 - James Schek
11
当然,记住微基准测试的陷阱总是很重要的,比如编译器/JVM优化可能会扭曲结果=8-) - Yuval
18
如果发生异常,endTime 不会被使用,因此不需要使用 finally 块。 - Peter Lawrey
显示剩余17条评论

255

我选择简单的答案。这对我来说有效。

long startTime = System.currentTimeMillis();

doReallyLongThing();

long endTime = System.currentTimeMillis();

System.out.println("That took " + (endTime - startTime) + " milliseconds");

它的效果相当不错。分辨率只有毫秒级别,使用System.nanoTime()可以得到更好的结果。两者都存在一些限制(比如操作系统调度时间片等),但这个方法效果非常棒。

多次运行后取平均值(运行次数越多越好),你就能得到一个不错的结果。


63
实际上,System.currentTimeMillis() 只有在大于15毫秒的情况下才准确可靠。对于非常低的值,它无法被信任。解决这个问题的方法(如前所述)是使用System.nanoTime()。 - Steve g
好的,我打算把这个作为官方答案,直到我读到 Steve g 的评论。很棒的小提示,Steve! - Ogre Psalm33
6
nanoTime()不能保证比currentTimeMillis()更准确,但是很多JVM实现确实使用nanoTime()具有更好的精度。 - James Schek
8
@JamesSchek,你真的需要注意措辞,正如我在其他地方提到的一样; nanoTime保证_至少_与currentTimeMillis一样精确。http://docs.oracle.com/javase/7/docs/api/java/lang/System.html#nanoTime%28%29 - arkon
1
currentTimeMillis 的一个小优点是它是一个实际的时间戳,可以用来记录开始/结束时间,而 nanoTime "只能用于测量经过的时间,与任何系统或挂钟时间的概念无关。" - Brad Parks
System.currentTimeMillis() 实际上已经被弃用于测量经过的时间。它被设计为返回准确的挂钟时间,但偶尔会出现跳动,例如在夏令时转换时向前/向后跳动一个小时,或者偶尔以秒为单位向前跳动一个“闰秒”。这些对于测量经过的时间来说是不好的。另一方面,System.nanoTime() 是专门设计用于测量经过的时间,并且保证不会向前或向后跳动。尽管如此,它通常是负数。因此,您始终需要计算两个时间之间的差值才能得到有效的持续时间。 - Charlie Reitzel

203

加油,大家!没有人提到使用Guava的做法(这种方法可能是很棒的):

Guava 的做法:

import com.google.common.base.Stopwatch;

Stopwatch timer = Stopwatch.createStarted();
//method invocation
LOG.info("Method took: " + timer.stop());

很好的一点是,Stopwatch.toString() 很好地选择了时间单位来进行测量。例如,如果值很小,它会输出 38 ns,如果很长,它会显示 5m 3s。

更好的是:

Stopwatch timer = Stopwatch.createUnstarted();
for (...) {
   timer.start();
   methodToTrackTimeFor();
   timer.stop();
   methodNotToTrackTimeFor();
}
LOG.info("Method took: " + timer);

注意:Google Guava 需要 Java 1.6 或更高版本


36
很不幸,Guava的计时器Stopwatch不是线程安全的。我曾经吃过这个亏。 - Dexter Legaspi
6
@DexterLegaspi,我非常想了解你的经验!愿意分享一下吗? - Siddhartha
1
在并行中使用秒表会导致您连续多次调用start()stop()同理)。 - Mingwei Samuel
KGF2让卡拉什尼科夫如此受欢迎,以至于我也用同样的名字称呼AK-47。哈哈 - taurus05
秒表/计时器是对System.nanoTime()的封装,所以+1。 - Charlie Reitzel

171

使用Java 8新API中的InstantDuration

Instant start = Instant.now();
Thread.sleep(5000);
Instant end = Instant.now();
System.out.println(Duration.between(start, end));

输出,

PT5S

2
谢谢,我怎样才能在输出结果时不让PT出现在前面? - java123999
1
方法的问题在于Instant不能提供毫秒和纳秒级别的精度。参考:https://dev59.com/CmIj5IYBdhLWcg3wCxF0 - prashantsunkari
10
你可以调用 Duration.between(start, end).getSeconds()Duration 还有其他方法可以将时间单位转换为毫秒,例如 toMillis() - Emil Lunde

160

将所有可能的方式汇集到一个地方。

日期

Date startDate = Calendar.getInstance().getTime();
long d_StartTime = new Date().getTime();
Thread.sleep(1000 * 4);
Date endDate = Calendar.getInstance().getTime();
long d_endTime = new Date().getTime();
System.out.format("StartDate : %s, EndDate : %s \n", startDate, endDate);
System.out.format("Milli = %s, ( D_Start : %s, D_End : %s ) \n", (d_endTime - d_StartTime),d_StartTime, d_endTime);

System.currentTimeMillis()

long startTime = System.currentTimeMillis();
Thread.sleep(1000 * 4);
long endTime = System.currentTimeMillis();
long duration = (endTime - startTime);  
System.out.format("Milli = %s, ( S_Start : %s, S_End : %s ) \n", duration, startTime, endTime );
System.out.println("Human-Readable format : "+millisToShortDHMS( duration ) );

易读的格式

public static String millisToShortDHMS(long duration) {
    String res = "";    // java.util.concurrent.TimeUnit;
    long days       = TimeUnit.MILLISECONDS.toDays(duration);
    long hours      = TimeUnit.MILLISECONDS.toHours(duration) -
                      TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(duration));
    long minutes    = TimeUnit.MILLISECONDS.toMinutes(duration) -
                      TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(duration));
    long seconds    = TimeUnit.MILLISECONDS.toSeconds(duration) -
                      TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration));
    long millis     = TimeUnit.MILLISECONDS.toMillis(duration) - 
                      TimeUnit.SECONDS.toMillis(TimeUnit.MILLISECONDS.toSeconds(duration));

    if (days == 0)      res = String.format("%02d:%02d:%02d.%04d", hours, minutes, seconds, millis);
    else                res = String.format("%dd %02d:%02d:%02d.%04d", days, hours, minutes, seconds, millis);
    return res;
}

Guava: Google秒表JAR « Stopwatch对象的目的是测量经过的时间,单位为纳秒。

com.google.common.base.Stopwatch g_SW = Stopwatch.createUnstarted();
g_SW.start();
Thread.sleep(1000 * 4);
g_SW.stop();
System.out.println("Google StopWatch  : "+g_SW);

Apache Commons LangJAR « StopWatch 提供了一个方便的API用于计时。

org.apache.commons.lang3.time.StopWatch sw = new StopWatch();
sw.start();     
Thread.sleep(1000 * 4);     
sw.stop();
System.out.println("Apache StopWatch  : "+ millisToShortDHMS(sw.getTime()) );

JODA-TIME

public static void jodaTime() throws InterruptedException, ParseException{
    java.text.SimpleDateFormat ms_SDF = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
    String start = ms_SDF.format( new Date() ); // java.util.Date

    Thread.sleep(10000);

    String end = ms_SDF.format( new Date() );       
    System.out.println("Start:"+start+"\t Stop:"+end);

    Date date_1 = ms_SDF.parse(start);
    Date date_2 = ms_SDF.parse(end);        
    Interval interval = new org.joda.time.Interval( date_1.getTime(), date_2.getTime() );
    Period period = interval.toPeriod(); //org.joda.time.Period

    System.out.format("%dY/%dM/%dD, %02d:%02d:%02d.%04d \n", 
        period.getYears(), period.getMonths(), period.getDays(),
        period.getHours(), period.getMinutes(), period.getSeconds(), period.getMillis());
}

Java 8中的日期时间APIDuration对象表示两个Instant对象之间的时间段。

Instant start = java.time.Instant.now();
    Thread.sleep(1000);
Instant end = java.time.Instant.now();
Duration between = java.time.Duration.between(start, end);
System.out.println( between ); // PT1.001S
System.out.format("%dD, %02d:%02d:%02d.%04d \n", between.toDays(),
        between.toHours(), between.toMinutes(), between.getSeconds(), between.toMillis()); // 0D, 00:00:01.1001 

Spring Framework 提供 StopWatch 实用类来测量 Java 中的流逝时间。

StopWatch sw = new org.springframework.util.StopWatch();
sw.start("Method-1"); // Start a named task
    Thread.sleep(500);
sw.stop();

sw.start("Method-2");
    Thread.sleep(300);
sw.stop();

sw.start("Method-3");
    Thread.sleep(200);
sw.stop();

System.out.println("Total time in milliseconds for all tasks :\n"+sw.getTotalTimeMillis());
System.out.println("Table describing all tasks performed :\n"+sw.prettyPrint());

System.out.format("Time taken by the last task : [%s]:[%d]", 
        sw.getLastTaskName(),sw.getLastTaskTimeMillis());

System.out.println("\n Array of the data for tasks performed « Task Name: Time Taken");
TaskInfo[] listofTasks = sw.getTaskInfo();
for (TaskInfo task : listofTasks) {
    System.out.format("[%s]:[%d]\n", 
            task.getTaskName(), task.getTimeMillis());
}

输出:

Total time in milliseconds for all tasks :
999
Table describing all tasks performed :
StopWatch '': running time (millis) = 999
-----------------------------------------
ms     %     Task name
-----------------------------------------
00500  050%  Method-1
00299  030%  Method-2
00200  020%  Method-3

Time taken by the last task : [Method-3]:[200]
 Array of the data for tasks performed « Task Name: Time Taken
[Method-1]:[500]
[Method-2]:[299]
[Method-3]:[200]

@DeepakPuthraya 那么,哪个库是适合生产使用的安全库? - Gaurav
1
@DeepakPuthraya,您可以使用Java 8提供的Java日期时间API,它非常简单。 - Yash
2
在我看来,如果每个解决方案都能展示系统输出的结果,这篇文章会更有价值。 - BAERUS
1
new Date().getTime()只是一个伪装成System.currentTimeMillis()的方法。new Date()new Date(System.currentTimeMillis())的作用相同,而getTime()将返回该long值(这是该类中唯一未被标记为弃用的功能)。同样,Calendar.getInstance().getTime()new Date()完全相同,但开销更大,因为它会准备此代码片段不使用的东西。 - Holger
不要使用第三方库,而是使用Java/Spring内置的库以获得更好的结果。如果在多个jar文件中都存在相同的类,则会导致错误。具体错误信息请参考此链接Caused by: java.lang.NoSuchMethodError: 'long com.google.common.base.Platform.systemNanoTime()' at com.google.common.base.Ticker$1.read(Ticker.java:60) ~[guava-18.0.jar:?], Stopwatch.start(Stopwatch.java:162) ~[guava-18.0.jar:?], Stopwatch.createStarted(Stopwatch.java:109) ~[guava-18.0.jar:?] - undefined
显示剩余2条评论

91

使用性能分析器(JProfiler、Netbeans Profiler、Visual VM、Eclipse Profiler等)。这样可以获得最准确的结果,同时对系统影响最小。这些分析器都使用内置的JVM机制进行性能分析,还能提供额外的信息,如堆栈跟踪、执行路径等,如果需要的话,还可以获得更全面的结果。

使用完全集成的性能分析器时,对一个方法进行性能分析非常简单。右键单击,选择性能分析器 -> 添加到根方法。然后像运行测试或调试器一样运行分析器即可。


这也是一个很好的建议,当我看到这个答案时,我有了那种“咦”灯泡亮起来的时刻。我们的项目使用JDeveloper,但我检查了一下,它确实有一个内置的分析器! - Ogre Psalm33
2
从Java 7 build 40开始(我想是这样),他们将以前的JRockits Flight Recorder包含到Java中(搜索Java Mission Control)。 - Niels Bech Nielsen
果然是 @NielsBechNielsen!http://www.oracle.com/technetwork/java/javaseproducts/mission-control/java-mission-control-1998576.html - Ogre Psalm33
如何通过Visual VM获取Java方法的执行情况,例如? - petertc

51

System.currentTimeMillis(); 不是衡量算法性能的好方法。它测量的是用户观察计算机屏幕所经历的总时间,其中包括后台运行的其他所有程序消耗的时间。如果您的工作站上有许多程序在运行,这可能会产生很大的差异。

正确的方法是使用 java.lang.management 包。

来自http://nadeausoftware.com/articles/2008/03/java_tip_how_get_cpu_and_user_time_benchmarking网站(存档链接):

  • "用户时间"是应用程序运行自己代码所花费的时间。
  • "系统时间"是在您的应用程序代表操作系统运行的代码所花费的时间(例如I/O)。

getCpuTime() 方法可以为您提供以上两者的总和:

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;

public class CPUUtils {

    /** Get CPU time in nanoseconds. */
    public static long getCpuTime( ) {
        ThreadMXBean bean = ManagementFactory.getThreadMXBean( );
        return bean.isCurrentThreadCpuTimeSupported( ) ?
            bean.getCurrentThreadCpuTime( ) : 0L;
    }

    /** Get user time in nanoseconds. */
    public static long getUserTime( ) {
        ThreadMXBean bean = ManagementFactory.getThreadMXBean( );
        return bean.isCurrentThreadCpuTimeSupported( ) ?
            bean.getCurrentThreadUserTime( ) : 0L;
    }

    /** Get system time in nanoseconds. */
    public static long getSystemTime( ) {
        ThreadMXBean bean = ManagementFactory.getThreadMXBean( );
        return bean.isCurrentThreadCpuTimeSupported( ) ?
            (bean.getCurrentThreadCpuTime( ) - bean.getCurrentThreadUserTime( )) : 0L;
    }

}

5
这确实是一个好观点,即“用户时间”(挂钟时间)并不总是衡量性能的好方式,特别是在多线程程序中。 - Ogre Psalm33
这就是我正在寻找的答案。 - ZhaoGang
同意和不同意“用户时间” - 有时正确的做法是测量代码本身所需的时间,而排除墙钟时间; 但其他时候应该测量总经过时间。 - Kaan
使用System.nanoTime()来计算经过的时间是我认为衡量单线程性能最好的方法。几乎总是可以通过调整每个线程使用的代码并使用较粗糙的方法来测量对整体吞吐量的影响,例如,您可以使用10个线程在1小时内索引50GB,而使用1个线程则只能在1小时内索引10GB或其他任何情况。 - Charlie Reitzel

43

这可能不是你想听到的,但这是AOP的一个很好的用法。在方法周围包装一个代理拦截器,在那里进行计时。

AOP的什么、为什么和如何超出了这个答案的范围,但这是我可能会采取的方式。

编辑:这里有一个链接,可以帮助你开始使用Spring AOP,如果你感兴趣的话。这是我接触过的Java中最易于使用的AOP实现。

此外,考虑到其他人的非常简单的建议,我应该补充说,AOP适用于当你不想让像计时这样的东西侵入你的代码中。但在许多情况下,这种简单易行的方法也是可以的。


4
这是一个关于如何使用Spring进行操作的教程:http://veerasundar.com/blog/2010/01/spring-aop-example-profiling-method-execution-time-tutorial/ - David Tinker

34

使用Java 8,您可以对每个普通的方法执行以下操作:

Object returnValue = TimeIt.printTime(() -> methodeWithReturnValue());
//do stuff with your returnValue

使用 TimeIt:

public class TimeIt {

public static <T> T printTime(Callable<T> task) {
    T call = null;
    try {
        long startTime = System.currentTimeMillis();
        call = task.call();
        System.out.print((System.currentTimeMillis() - startTime) / 1000d + "s");
    } catch (Exception e) {
        //...
    }
    return call;
}
}

使用这种方法,您可以在代码中任何地方轻松进行时间测量,而不会破坏它。在这个简单的例子中,我只是打印时间。您可以添加一个TimeIt开关,例如仅在DebugMode中打印时间。
如果您正在使用函数,可以像这样做:
Function<Integer, Integer> yourFunction= (n) -> {
        return IntStream.range(0, n).reduce(0, (a, b) -> a + b);
    };

Integer returnValue = TimeIt.printTime2(yourFunction).apply(10000);
//do stuff with your returnValue

public static <T, R> Function<T, R> printTime2(Function<T, R> task) {
    return (t) -> {
        long startTime = System.currentTimeMillis();
        R apply = task.apply(t);
        System.out.print((System.currentTimeMillis() - startTime) / 1000d
                + "s");
        return apply;
    };
}

这比其他解决方案看起来好多了。它更接近于Spring AOP,但比它更轻巧。真正的Java 8方式!+1 谢谢! - Amit Kumar
也许这看起来很好,因为Stefan正在使用新的Java函数。但我认为这是不必要的难以阅读和理解。 - Stimpson Cat

21

我们还可以使用Apache commons的StopWatch类来测量时间。

示例代码:

org.apache.commons.lang.time.StopWatch sw = new org.apache.commons.lang.time.StopWatch();

System.out.println("getEventFilterTreeData :: Start Time : " + sw.getTime());
sw.start();

// Method execution code

sw.stop();
System.out.println("getEventFilterTreeData :: End Time : " + sw.getTime());

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