Java中的时间测量开销

Posted

技术标签:

【中文标题】Java中的时间测量开销【英文标题】:Time measuring overhead in Java 【发布时间】:2011-08-04 04:01:34 【问题描述】:

在低级别测量经过时间时,我可以选择使用以下任何一种:

System.currentTimeMillis();
System.nanoTime();

这两种方法都实现了native。在深入研究任何 C 代码之前,是否有人知道调用其中一个或另一个是否有任何实质性开销?我的意思是,如果我真的不关心额外的精度,哪一个会占用更少的 CPU 时间?

注意:我使用的是标准 Java 1.6 JDK,但这个问题可能对任何 JRE 都有效...

【问题讨论】:

请注意,nanoTime 不会实时映射,只能用于衡量某件事花费了多长时间。 @R.Bemrose,谢谢,我知道。这真的是为了衡量某件事需要多长时间。 【参考方案1】:

此页面上标记为正确的答案实际上是不正确的。由于 JVM 死代码消除 (DCE)、堆栈上替换 (OSR)、循环展开等原因,这不是编写基准测试的有效方法。只有像 Oracle 的 JMH 微基准测试框架这样的框架才能正确测量类似的东西。如果您对此类微基准测试的有效性有任何疑问,请阅读this post。

这是 System.currentTimeMillis()System.nanoTime() 的 JMH 基准测试:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class NanoBench 
   @Benchmark
   public long currentTimeMillis() 
      return System.currentTimeMillis();
   

   @Benchmark
   public long nanoTime() 
    return System.nanoTime();
   

以下是结果(在 Intel Core i5 上):

Benchmark                            Mode  Samples      Mean   Mean err    Units
c.z.h.b.NanoBench.currentTimeMillis  avgt       16   122.976      1.748    ns/op
c.z.h.b.NanoBench.nanoTime           avgt       16   117.948      3.075    ns/op

这表明System.nanoTime() 在每次调用约 118ns 时比约 123ns 略快。然而,同样清楚的是,一旦将平均误差考虑在内,两者之间的差异非常小。结果也可能因操作系统而异。但总体来说应该是它们在开销方面基本上是相等的。

更新 2015/08/25:虽然这个答案更接近于纠正大多数,但使用 JMH 来衡量,它仍然不正确。测量像System.nanoTime() 这样的东西本身就是一种特殊的扭曲基准测试。答案和权威文章是here。

【讨论】:

JMH 的精彩使用!我希望这个网站上的更多微基准能够利用它。 我想试试你的例子,想知道为什么@GenerateMicroBenchmark 注释不起作用。它似乎最近或多或少地重命名为@Benchmark【参考方案2】:

我认为您无需担心两者的开销。它是如此之小,以至于它本身几乎无法测量。以下是两者的快速微基准:

for (int j = 0; j < 5; j++) 
    long time = System.nanoTime();
    for (int i = 0; i < 1000000; i++) 
        long x = System.currentTimeMillis();
    
    System.out.println((System.nanoTime() - time) + "ns per million");

    time = System.nanoTime();
    for (int i = 0; i < 1000000; i++) 
        long x = System.nanoTime();
    
    System.out.println((System.nanoTime() - time) + "ns per million");

    System.out.println();

最后的结果:

14297079ns per million
29206842ns per million

似乎System.currentTimeMillis() 的速度是System.nanoTime() 的两倍。然而 29ns 将比你无论如何测量的任何东西都要短得多。我会选择 System.nanoTime() 以获得精确度和准确性,因为它与时钟无关。

【讨论】:

不错的基准测试。在我的电脑上,它是相反的。我得到这个输出:每百万 17258920ns,每百万 14974586ns。这意味着它实际上取决于 JVM、处理器、操作系统等。除此之外,差异几乎无关紧要。谢谢你的好答案! 没问题。是的,我敢肯定有各种各样的因素会导致它发生变化。无论哪种方式,您都必须在现代机器上每秒调用数百万次才能导致明显的计时开销。 底层操作系统会很有趣。我怀疑如果实现使用 POSIX 的 clock_gettime(),则差异将是 ~0。 是的。但是您的外部循环似乎表明结果在同一系统上有些一致。除了可能有一些 JVM 开销的第一次迭代之外,所有迭代都会产生大致相同的值......所以选择 nanoTime() 并获得一点额外的精度是安全的。 不使用变量 X,因此可以优化获取时间的系统调用。在稍后的状态下,可以看到循环内没有完成任何工作,它们也可以被优化掉。留下很少的东西来衡量。微基准和 JIT 的危险。【参考方案3】:

您应该只使用System.nanoTime() 来衡量运行某项所需的时间。这不仅仅是纳秒精度的问题,System.currentTimeMillis() 是“挂钟时间”,而System.nanoTime() 用于计时,没有其他“真实世界时间”的怪癖。来自System.nanoTime()的Javadoc:

此方法只能用于测量经过的时间,与系统或挂钟时间的任何其他概念无关。

【讨论】:

是的,我知道 Javadoc 提到了这一点。但是,如果您多次测量 1-10 毫秒之间的事物,两者都可以等效使用。您所说的“现实世界时间怪癖”是什么意思? @Lukas:问题是System.currentTimeMillis() 可以(我相信)做一些事情,比如插入小的时钟调整,这会导致计时。可能很少见,但nanoTime() 用于不应受此类事情影响的测量。 @Lukas:实际上,它基本上只是反映了您的计算机显示的系统时间。在使用currentTimeMillis() 的时间期间将计算机上的日期更改为一周前,您将得到一个不错的负数! =) nanoTime() 不是这样。 这不会发生,但很好的想法:-) 您需要担心的不仅仅是手动更改时间。还有夏令时,以及在与互联网上的服务器核对后自动调整的时钟。【参考方案4】:

System.currentTimeMillis() 通常非常快(afaik 5-6 cpu 周期,但我不知道我在哪里读过这个),但它的分辨率在不同的平台上有所不同。

因此,如果您需要高精度,请选择nanoTime(),如果您担心开销,请选择currentTimeMillis()

【讨论】:

好点。查看 WhiteFang34 的答案。它们似乎同样快,具体取决于用于对其进行基准测试的系统。【参考方案5】:

如果你有时间,请看this talk by Cliff Click,他谈到了System.currentTimeMillis 的价格以及其他事情。

【讨论】:

【参考方案6】:

这个问题的公认答案确实不正确。 @brettw 提供的替代答案很好,但细节仍然很简单。

有关此主题的完整处理和这些电话的实际费用,请参阅https://shipilev.net/blog/2014/nanotrusting-nanotime/

回答问题:

有谁知道调用一个或另一个是否有任何重大开销?

调用System#nanoTime 的开销在每次调用15 到30 纳秒之间。 nanoTime 报告的值,其分辨率,每 30 纳秒仅更改一次

这意味着取决于您是否尝试每秒处理数百万个请求,调用 nanoTime 意味着您实际上会丢失第二次调用 nanoTime 的很大一部分。对于此类用例,请考虑测量来自客户端的请求,从而确保您不会陷入coordinated omission,测量队列深度也是一个很好的指标。

如果您不想在一秒钟内完成尽可能多的工作,那么nanoTime 就无关紧要,但协调的遗漏仍然是一个因素。

最后,为了完整起见,currentTimeMillis 不管它的成本是多少都不能使用。这是因为不能保证在两个呼叫之间前进。尤其是在带有 NTP 的服务器上,currentTimeMillis 会不断移动。更不用说计算机测量的大多数东西都不需要整整一毫秒。

【讨论】:

感谢您的反馈。与此同时,我还偶然发现了 Aleksey Shipilëv 的那篇文章。您是否介意本着 Stack Overflow 的精神将那篇文章的精髓作为对我具体 Stack Overflow 问题的回答(即有一个完整的答案,可能直接在 Stack Overflow 上的链接中得到更多详细信息的支持)?那我接受你的回答。 我这样做不是为了得到一个被接受的答案。最终的问题是,公认的答案是把人们引向错误的方向,这是不幸的。 自你问起就更新了,但同时,我总是觉得 *** 像这样的答案很短,因为它们总是给解释留下太多空间【参考方案7】:

在理论上,对于使用本机线程并位于现代抢占式操作系统上的 VM,currentTimeMillis 可以实现为每个时间片仅读取一次。据推测,nanoTime 实现不会牺牲精度。

【讨论】:

这是关于精度的一个好点。如果你是对的,那么currentTimeMillis() 再精确不过了。或者因为它不需要精确,所以可以这样实现。但另一方面,这并没有说明哪个更快......【参考方案8】:

currentTimeMillis() 的唯一问题是,当您的 VM 调整时间时(这通常会自动发生)currentTimeMillis() 会随之而来,从而产生不准确的结果,尤其是对于基准测试。

【讨论】:

以上是关于Java中的时间测量开销的主要内容,如果未能解决你的问题,请参考以下文章

如何测量模拟框架 (TypeMock) 的开销?

Java虚拟机的启动时间由啥组成?

测量代码执行时间的“快速”方法

测量linux内核中函数的执行时间

java中的三边测量示例

了解 JVM 中的对象开销