精度与精度

我想知道的是在更新对象在游戏中的位置时应该使用System.currentTimeMillis()还是System.nanoTime()吗?它们的运动变化与自上次调用以来经过的时间成正比,我想尽可能地精确。

我读到不同操作系统之间存在一些严重的时间分辨率问题(即Mac / Linux的分辨率几乎为1毫秒,而Windows的分辨率为50毫秒?)。我主要是在Windows上运行我的应用,而50ms分辨率似乎很不准确。

有没有比我列出的两个更好的选择?

有什么建议/评论吗?

评论

nanoTime通常通常比currentTimeMillis更为准确,但它也是一个相对昂贵的调用。currentTimeMillis()运行在几个(5-6)cpu时钟中,nanoTime取决于基础架构,并且可以是100+ cpu时钟。

大家都知道Windows通常具有1000ms / 64的时间片粒度,对吗?这是15.625毫秒,即15625000纳秒!

我认为,额外的100个时钟周期不会影响您的游戏,因此折衷可能值得。您每次游戏更新仅应调用一次该方法,然后将值保存在mem中,这样就不会增加太多开销。至于不同平台的粒度,我不知道。

Windows的默认时间片粒度为1000ms / 64。您可以通过本地的timeBeginPeriod API来增加它。除了基本计时器外,现代PC还具有高分辨率计时器。可通过QueryPerformanceCounter调用访问高分辨率计时器。

@Gohan-本文详细介绍了System.currentTimeMillis()的内部工作原理:pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html

#1 楼

如果您只是想对经过的时间进行非常精确的测量,请使用System.nanoTime()System.currentTimeMillis()将为您提供自该纪元以来最准确的经过时间(以毫秒为单位),但是System.nanoTime()为您提供相对于某个任意点的纳秒级精确时间。

来自Java文档:


public static long nanoTime()


返回最精确的可用系统计时器的当前值,以纳秒为单位。

此方法只能用于
测量经过时间,并且
与系统的任何其他概念或钟表时间都不相关。返回的值
表示纳秒,因为某些
是固定的但任意的起始时间(可能在
以后,所以值可能为
负)。此方法提供
纳秒精度,但不一定提供纳秒精度。没有
保证
值如何频繁变化。连续呼叫中的差异
大于大约292年(263
纳秒),将无法准确地
由于数值
溢出而计算经过时间。


例如,测量执行某些代码所需的时间:

long startTime = System.nanoTime();    
// ... the code being measured ...    
long estimatedTime = System.nanoTime() - startTime;


另请参阅:JavaDoc System.nanoTime()和JavaDoc System .currentTimeMillis()了解更多信息。

评论


您确定知道准确性和准确性之间的区别吗?它不可能精确到纳秒级的精度。

– mmcdole
08年12月9日在2:09

对不起,我是说精确。我使用的术语比较松散,但我同意它会造成混淆(以及该词的使用不当)。

– dancavallaro
08年12月9日在2:10

@dancavallaro,感谢您的信息。如果您不介意,我会编辑您的答案以包含文档中的引号,并修复链接

– mmcdole
08年12月9日在2:28

在选择nanoTime()时,此答案在技术上是正确的,但在一个非常重要的方面完全没有意义。正如文档所说,nanoTime()是一个精密计时器。 currentTimeMillis()不是计时器,它是“挂钟”。 nanoTime()总是会产生正的经过时间,currentTimeMillis不会(例如,如果您更改日期,hit秒等),这对于某些类型的系统来说是非常重要的区别。

– charstar
2011年4月10日上午10:32

用户更改时间和NTP同步可以确定,但是为什么currentTimeMillis由于DST而更改? DST开关不会更改经过纪元的秒数​​。它可能是“挂钟”,但它是基于UTC的时钟。您必须根据时区和DST设置确定本地时间的含义(或使用其他Java实用程序为您完成此操作)。

–影人
2014年1月24日21:43

#2 楼

由于没有人提到过这个问题……

比较不同线程之间的System.nanoTime()调用的结果并不安全。即使线程的事件以可预测的顺序发生,纳秒级的差异也可以是正数或负数。

System.currentTimeMillis()可在线程之间安全使用。

评论


在仅保留至SP2的Windows上,根据:stackoverflow.com/questions/510462/…

– Peter Schmitz
2012-2-22在11:25

好吧,您每天都会学到一些新东西。但是我怀疑,由于过去它是不安全的(肯定会在线程间返回无意义的读数),所以这种用法可能仍超出规范范围,因此应避免使用。

– GUB
2012年3月4日14:55

@jgubby:非常有趣...对支持的引用,在不同线程之间比较System.nanoTime()调用的结果并不安全?以下链接值得一看:bugs.sun.com/bugdatabase/view_bug.do?bug_id=6519418docs.oracle.com/javase/7/docs/api/java/lang/…

–user454322
2012年10月9日在10:45

看一下这里提到的描述:docs.oracle.com/javase/7/docs/api/java/lang/…,看起来只要从相同的时间从nanoTime返回的值之间的差异就可以比较有效JVM-仅当计算在Java虚拟机的同一实例中获得的两个此类值之间的差时,此方法返回的值才有意义。

–Tuxdude
16 Jan 20'23:01



用于nanoTime()的JavaDoc说:在Java虚拟机的实例中,此方法的所有调用都使用相同的来源。其他虚拟机实例可能使用其他来源。这意味着它确实跨线程返回相同的结果。

–西蒙·福斯伯格
18年2月1日在16:02

#3 楼

由Arkadiy更新:在Oracle Java 8中,我已经观察到Windows 7上System.currentTimeMillis()的更正确的行为。该时间以1毫秒的精度返回。 OpenJDK中的源代码没有更改,因此我不知道是什么导致了更好的行为。


Sun的David Holmes在几年前发布了一篇博客文章,其外观非常详细。在Java计时API(尤其是System.currentTimeMillis()System.nanoTime())上,何时使用它们以及它们如何在内部工作。

在Hotspot VM内部:时钟,计时器和调度事件-第一部分- Windows

Windows上Java为具有定时等待参数的API使用的计时器的一个非常有趣的方面是,计时器的分辨率可以根据可能进行的其他API调用而改变-系统(不仅在特定过程中)。他显示了一个示例,其中使用Thread.sleep()会导致此分辨率更改。

评论


@Arkadiy:您在更新中是否有声明的来源?

– Lii
2015年12月15日在17:40

@Lii-不幸的是,没有。它来自于我在以下问题中运行代码:stackoverflow.com/questions/34090914/…。该代码在Java 7中产生15毫秒的精度,在Java 8中产生1毫秒的精度。

–user3458
15年12月15日在18:01

我在OpenJDK的hotspot / src / os / windows / vm / os_windows.cpp中将currentTimeMillis跟踪到os :: javaTimeMillis(hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/os / ...) 。看起来它仍然是GetSystemTimeAsFileTime,所以我不知道更改来自何处。或者,即使它是有效的。使用前进行测试。

–user3458
15年12月16日在17:44

行为上的变化是更改GetSystemTimeAsFileTime在XP vs 7中的工作方式的结果。有关更多详细信息,请参见此处。(tl; dr变得更加精确,因为整个系统引入了一些更精确的计时方法)。

–user719662
17年7月18日在22:08

#4 楼

旧版JVM不支持System.nanoTime()。如果这是一个问题,请坚持使用currentTimeMillis关于精度,您几乎是正确的。在某些Windows机器上,currentTimeMillis()的分辨率约为10毫秒(而非50毫秒)。我不确定为什么,但是某些Windows机器和Linux机器一样准确。

我过去使用GAGETimer取得了一定的成功。

评论


“旧版JVM”和哪个? Java 1.2之类的?

–西蒙·福斯伯格
17-10-20在15:07

System.nanoTime()于2004年在Java 1.5中引入。2013年Java 1.4扩展了对它的支持,因此可以肯定地说System.nanoTime()现在可以依靠,并且这个答案现在已经过时了。

–戴夫L。
1月22日下午16:45

#5 楼

就像其他人所说的那样,currentTimeMillis是时钟时间,它是由于夏令时而改变的(不是:夏令时和时区与currentTimeMillis无关,其余的都是事实),用户更改时间设置,leap秒和Internet时间同步。如果您的应用依赖于单调增加的经过时间值,那么您可能更喜欢nanoTime。
您可能会认为玩家不会在游戏过程中摆弄时间设置,也许您是对的。但是,请不要低估由于Internet时间同步或远程桌面用户造成的中断。 nanoTime API不受这种干扰的影响。
如果您想使用时钟时间,但要避免由于Internet时间同步而导致中断,则可以考虑使用诸如Meinberg之类的NTP客户端,它可以将时钟速率“调整”到将其归零,而不仅仅是定期重置时钟。
我是根据个人经验讲的。在我开发的天气应用程序中,我得到了随机出现的风速峰值。我花了一段时间才意识到典型的PC上的时钟时间行为干扰了我的时基。当我开始使用nanoTime时,我所有的问题都消失了。一致性(单调性)对我的应用程序比原始精度或绝对精度更重要。

评论


“ currentTimeMillis是时钟时间,它会由于夏时制而改变”。 System.currentTimeMillis()报告自Unix / Posix纪元(即UTC 1970年1月1日午夜)以来的经过时间(以毫秒为单位)。因为从不为夏令时调整UTC,所以当当地时区增加时或以夏时制为当地时间设置偏移时,该值将不会被抵消。此外,Java Time Scale消除了leap秒,因此日期似乎继续具有86,400秒。

–斯科特
16年5月23日在5:27

#6 楼

是的,如果需要这种精度,请使用System.nanoTime(),但是请注意,您那时需要Java 5+ JVM。

在我的XP系统上,我看到系统时间至少报告为100微秒278纳秒。以下代码:

private void test() {
    System.out.println("currentTimeMillis: "+System.currentTimeMillis());
    System.out.println("nanoTime         : "+System.nanoTime());
    System.out.println();

    testNano(false);                                                            // to sync with currentTimeMillis() timer tick
    for(int xa=0; xa<10; xa++) {
        testNano(true);
        }
    }

private void testNano(boolean shw) {
    long strMS=System.currentTimeMillis();
    long strNS=System.nanoTime();
    long curMS;
    while((curMS=System.currentTimeMillis()) == strMS) {
        if(shw) { System.out.println("Nano: "+(System.nanoTime()-strNS)); }
        }
    if(shw) { System.out.println("Nano: "+(System.nanoTime()-strNS)+", Milli: "+(curMS-strMS)); }
    }


#7 楼

对于游戏图形和平滑的位置更新,请使用System.nanoTime()而不是System.currentTimeMillis()。我在游戏中从currentTimeMillis()切换为nanoTime(),并在视觉上改善了运动的平滑度。

虽然一毫秒似乎应该已经很精确了,但在视觉上却不是。 nanoTime()可以改善的因素包括:


在壁钟分辨率以下的精确像素定位
如果需要,像素之间抗锯齿的能力
Windows壁钟不准确
时钟抖动(实际时钟向前滴答的时间不一致)

其他答案表明,如果反复调用,nanoTime确实会降低性能,因此最好将其称为每帧一次,并使用相同的值来计算整个帧。

#8 楼

我在nanotime方面有很好的经验。使用JNI库,它可以将挂钟时间提供为两个很长的时间(从纪元开始的秒数,在该秒数秒内为纳秒)。它与针对Windows和Linux预先编译的JNI部件一起提供。

#9 楼

System.currentTimeMillis()对于经过的时间并不安全,因为此方法对系统的系统实时时钟更改敏感。
应使用System.nanoTime
请参考Java系统帮助:

关于nanoTime方法:


..此方法提供纳秒级精度,但不一定是
纳秒分辨率(即值更改的频率)-没有保证,除非分辨率至少等于currentTimeMillis()的分辨率。 。


如果使用System.currentTimeMillis(),则经过时间可能为负(返回<-返回到未来)

#10 楼

这里的一件事是nanoTime方法的不一致。对于相同的输入,它不会给出非常一致的值。currentTimeMillis在性能和一致性方面要好得多,并且尽管不如nanoTime精确,但误差范围较小,因此其值的准确性更高。因此,我建议您使用currentTimeMillis

评论


如其他答案和注释中所述,currentTimeMillis会随系统时钟的变化而变化,因此自JVM中发生某个较早的事件以来,计算经过的时间的选择就很差。

–都灵标记
13-10-14在19:00