System.nanoTime()的精度与准确性

26

System.nanoTime()的文档中有以下说明(重点标注如下)。

此方法仅用于测量经过的时间,与系统或挂钟时间的任何其他概念无关。返回的值表示自某个固定但任意时间以来的纳秒数(可能在未来,因此值可能为负)。此方法提供纳秒精度,但不一定提供纳秒准确度。不保证值的更改频率。

我认为可以有两种不同的解释:

  1. 上面加粗的句子是指单个返回值。然后,在数值意义上理解精度和准确性。精度是指有效数字的位数-截断的位置,准确性是指数字是否正确(例如在这里描述的)。

  2. 上面加粗的句子是指该方法本身的能力。然后,精度和准确性应按镖靶类比法解释(http://en.wikipedia.org/wiki/Precision_vs._accuracy#Accuracy_versus_precision:_the_target_analogy)。 因此,低准确性,高精度=>用高精度不断击中错误的值:假设物理时间静止,连续调用nanoTime()将返回相同的数值,但它与自参考时间以来经过的实际时间存在一些恒定偏差。

哪种解释是正确的呢?我的观点是,如果采用解释2,则使用nanoTime()(通过减去两个返回值)计算时间差异将正确到纳秒(因为测量中的常量误差/偏移量将被消除),而解释1无法保证这种测量的符合性,因此不一定意味着时间差测量的高精度。


更新于2013年4月15日:Java 7关于System.nanoTime()的文档已经更新,以解决之前措辞可能引起的困惑。

返回当前运行的Java虚拟机的高分辨率时间源的值,以纳秒为单位。

该方法只能用于测量经过的时间,并与系统或挂钟时间的任何其他概念无关。返回的值表示自某个固定但任意的起始时间以来的纳秒数(也许在未来,因此值可能为负)。同一个Java虚拟机实例中的所有调用此方法的实例都使用相同的起源;其他虚拟机实例可能使用不同的起源。

该方法提供了纳秒级别的精度,但不一定提供纳秒分辨率(即值更改的频率)——除了分辨率至少与currentTimeMillis()的分辨率相同之外,没有任何保证。

连续调用之间的差异跨越大约292年(263纳秒)的情况下,由于数字溢出,将无法正确计算经过的时间。

只有在计算同一Java虚拟机实例中获得的两个这样的值之间的差异时,此方法返回的值才具有意义。


1
据我所知,该函数取决于操作系统返回纳秒的可能性。因此,假设你得到了 x = nanoTime(); 这将返回一些值,假设为 958145。如果你在正好一纳秒后调用 nanoTime(),你不能保证得到 958146。这意味着它返回的是纳秒,但不一定是两次调用之间经过的正确纳秒数。 - Bobby
1
哇 - 这是一个非常好的问题,涉及到一个重要而有趣的话题! - Darrell Teague
请参阅Wikipedia - False Precision - Pacerier
2
如果我为长期太空任务编写软件,了解292年的时间是很有用的。 - Aftershock
4个回答

12

在Clojure命令行中,我得到了以下信息:

user=> (- (System/nanoTime) (System/nanoTime))
0
user=> (- (System/nanoTime) (System/nanoTime))
0
user=> (- (System/nanoTime) (System/nanoTime))
-641
user=> (- (System/nanoTime) (System/nanoTime))
0
user=> (- (System/nanoTime) (System/nanoTime))
-642
user=> (- (System/nanoTime) (System/nanoTime))
-641
user=> (- (System/nanoTime) (System/nanoTime))
-641
所以,实际上,nanoTime并不会每纳秒更新一次,这与其精度所暗示的直觉相反。在Windows系统中,它在底层使用了QueryPerformanceCounter API(根据这篇文章),实际上似乎只能提供约640 ns的分辨率(在我的系统中!)。
请注意,由于nanoTime的绝对值是任意的,因此它本身不能具有任何准确性。仅对连续nanoTime调用之间的差异有意义。该差异的(不)准确性大约在1微秒左右。

2
非常好的答案,而且是真的。我认为这里重要的部分是Java开发人员意识到操作系统“时钟”时间(来自芯片和操作系统缓存)存在各种问题,从漂移到准确性和延迟(在各种系统上仅每+/- 30毫秒更新一次)。因此,“nanoTime”实现实际上是非常不同的-不使用时钟芯片,而是使用“高性能计时器”实现,在操作系统级别上有所不同,但结果是相同的-定时器具有比旧的+/- 30毫秒缓存/漂移更准确的分辨率。 - Darrell Teague

10

第一种解释是正确的。在大多数系统上,最低有效位数将始终为零。这实际上提供了微秒级别的精度,但以纳秒的固定精度报告。

事实上,在我再次查看它时,您的第二种解释也是正在发生的有效描述,甚至可能更加有效。想象时间被冻结,报告将始终是相同错误的纳秒数,但如果理解为微秒的整数,则是正确的。


所以我只应该相信使用nanoTime()进行微秒级别的时间差测量。感谢两位回答者! - andreasdr
正确的做法是只有两个值之间的ABS()差异才会产生有意义的结果,这在一些高性能计时器的操作系统实现中提供。因此,这个函数不是关于“现在几点”,而是关于计时器,因此比较两个值才是它的用途(但确实非常有用)。 - Darrell Teague

5
System.currentTimeMillis()System.nanoTime()之间的区别是非常有趣的。其中一个特点是System.nanoTime()不会随着墙上挂钟的变化而改变。我在运行 Windows 虚拟机上的代码时,发现存在时间漂移问题。每次当NTP更正时间漂移时,System.currentTimeMillis()可能会向前或向后跳动1-2秒,使得准确时间戳毫无意义(适用于Windows2003、2008 VPS版本)。

System.nanoTime()不受墙上挂钟时间的影响,因此您可以获取通过 NTP 检索到的时间并根据 System.nanoTime() 自上次检查 NTP 以来的情况进行修正,从而获得比在恶劣的墙上钟条件下使用 System.currentTimeMillis() 更加准确的时间。

这当然是违反直觉的,但却很有用。


1
这可能解释了为什么在Windows 7下运行Java测试时,使用System.getNanoTime()方法Java报告了一个时间差为350777 ns(相当于0.35毫秒),但是使用System.currentTimeMillis()方法在同一时间段内报告了一个时间差为15毫秒。看起来使用System.currentTimeMillis()方法进行基准测试可能会完全不准确。 - CausingUnderflowsEverywhere

3

如果像我这样的人反复阅读这个问题,还是有点理解困难,那么这里有一个更简单(希望如此)的解释。

精度是指保留多少位数字。以下是每个数字对应的示例:

long start = System.nanoTime();
long end   = System.nanoTime();

这将是一个精确的数字(有很多位数)。

由于“准确性”只是相对于某些事物衡量的,因此单个调用System.nanoTime没有意义,因为它的值相当任意,并且不依赖于我们可以测量的任何东西。区分其准确性的唯一方法是进行两个不同的调用,如下所示:

 long howMuch = end - start;

它不会拥有纳秒级的精度。实际上,在我的机器上,差异为0.2-0.3微秒。


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