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中的时间测量开销的主要内容,如果未能解决你的问题,请参考以下文章