什么是Java中代码执行时间计时的简洁方法?

10

计时代码执行可以很方便地了解事情需要多长时间。然而,我发现通常这样做的方式很草率,因为它应该具有相同的缩进,这使得更难以阅读实际计时的内容。

long start = System.nanoTime();

// The code you want to time

long end = System.nanoTime();
System.out.printf("That took: %d ms.%n", TimeUnit.NANOSECONDS.toMillis(end - start));

一次尝试

我想到了以下方法,它看起来更好,有几个优点和缺点:

优点:

  • 由于缩进,很清楚正在计时的内容
  • 代码完成后,它将自动打印出花费的时间

缺点:

  • 这不是使用AutoClosable的正确方式(相当确定)
  • 它创建了一个新的TimeCode实例,这不好
  • try块中声明的变量无法在其外部访问

可以像这样使用:

 try (TimeCode t = new TimeCode()) {
     // The stuff you want to time
 }

使这成为可能的代码是:

class TimeCode implements AutoCloseable {

    private long startTime;

    public TimeCode() {
        this.startTime = System.nanoTime();
    }

    @Override
    public void close() throws Exception {
        long endTime = System.nanoTime();
        System.out.printf("That took: %d ms%n",
                TimeUnit.NANOSECONDS.toMillis(endTime - this.startTime));
    }

}

问题

我的问题是:

  • 我的方法是否真的像我想象中的那样糟糕?
  • 在Java中,有没有更好的计时代码执行的方法,可以清楚地看到正在计时的内容,或者我只能接受像我第一个代码块那样的东西。

1
你应该使用 System.nanoTime() 而不是 System.currentTimeMillis()。(链接) - Turamarth
2
@Henry 你好,我认为这不是重复的问题,因为它并不是关于微基准测试的,我不想测试代码运行的快慢。而更多的是关于日志记录,无论是将其记录到文件还是控制台,以指示在生产环境中发生了什么事情的速度有多快。 - Mark
1
在一个应用程序容器中,您可以拥有一个方法 interceptor 注释来触发性能测量。 - SME_Dev
1
@RyanTheLeach 我认为这不是同一个问题,那个问题是关于如何计时代码的,而这个问题是关于更清晰、更易读的计时代码方式,这个问题已经表明了对如何计时代码的了解。 - Mark
1
说句实话,我没有标记一个近似重复而不是完全重复的问题。(也许使用“接近重复”会更好) - Ryan Leach
显示剩余4条评论
3个回答

3
你的解决方案很好。
一种不太表达的方法是将要计时的代码封装在lambda中。
public void timeCode(Runnable code) {
    ...
    try {
        code.run();
    } catch ...
    }
    ...
}

timeCode(() -> { ...code to time... });

您可能希望捕获检查异常并将它们传递给一些运行时异常或其他异常。


我真的很喜欢这个解决方案,你的评论/编辑是指你添加类似于throw new RuntimeException(e)的内容,其中e是被捕获的异常,对吗? - Mark
@Mark 是的,已经有类似于流使用的异常了,但我记不清是哪一个了。与其使用 RuntimeException,我更倾向于使用 IllegalStateException 或类似的异常。 - Joop Eggen

1
你的方法非常好。我们专业使用类似的方法,但是用C#编写。
我可能会添加一些东西,比如适当的日志支持,这样你可以切换那些性能数字,或者将它们设置为调试或信息级别。
我考虑的其他改进是创建一些静态应用程序状态(滥用线程本地变量),这样你就可以嵌套这些部分,并有摘要统计。
请参见 https://github.com/aikar/minecraft-timings,这是一个在Minecraft模组中实现此功能的库(用Java编写)。

如果我选择这条路,那么这些就是我需要考虑的事情,像这样的事情,这只是一个最简单的例子。因此,创建一个新的对象实例只是你必须要做的事情吗? - Mark
Java的逃逸分析通常非常出色,因此创建单个对象用于似乎需要测量和输出的操作而成为性能考虑是罕见的,这不应该优先考虑比较好的语法。话虽如此,您可以使用一对静态方法的开始/停止来减少分配,但这样做会相当容易出错。 - Ryan Leach

1

我认为在问题描述中建议的解决方案过于间接和不符合惯用语法,不适合生产代码。(但看起来是一个很好的工具,在开发过程中快速测量时间。)

无论是Guava项目还是Apache Commons都包含计时器类。如果使用其中任何一个,代码将更易于阅读和理解,并且这些类还具有更多内置功能。

即使不使用try-with-resource语句,正在被测量的部分也可以封装在一个块中以提高清晰度:

// Do things that shouldn't be measured

{
    Stopwatch watch = Stopwatch.createStarted();

    // Do things that should be measured

    System.out.println("Duration: " + watch.elapsed(TimeUnit.SECONDS) + " s");
}

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