首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >System.nanoTime对System.currentTimeMillis

System.nanoTime对System.currentTimeMillis
EN

Stack Overflow用户
提问于 2014-01-01 23:10:26
回答 2查看 2.5K关注 0票数 7

根据它的文档,System.nanoTime返回一些固定但任意的起始时间后的纳秒。然而,在所有的x64机器上,我尝试了下面的代码,有时间跳跃,移动固定的原点时间。我的方法中可能存在一些缺陷,无法使用另一种方法(这里是currentTimeMillis)获得正确的时间。然而,测量相对时间(持续时间)的主要目的也受到负面影响。

当比较不同的队列与LMAX的Disruptor时,我遇到了这个问题,试图度量延迟,在LMAX的Disruptor中,有时会出现非常负面的延迟。在这些情况下,开始和结束时间戳是由不同的线程创建的,但是延迟是在这些线程完成之后计算出来的。

这里的代码需要时间使用nanoTime,用currentTimeMillis时间计算固定的原点,并比较调用之间的起源。既然我必须在这里问一个问题:这个代码有什么问题?为什么它违反了固定的原产地合同?或者不是?

代码语言:javascript
复制
import java.text.*;

/**
 * test coherency between {@link System#currentTimeMillis()} and {@link System#nanoTime()}
 */
public class TimeCoherencyTest {

    static final int MAX_THREADS = Math.max( 1, Runtime.getRuntime().availableProcessors() - 1);
    static final long RUNTIME_NS = 1000000000L * 100;
    static final long BIG_OFFSET_MS = 2;

    static long startNanos;
    static long firstNanoOrigin;
    static {
        initNanos();
    }

    private static void initNanos() {
        long    millisBefore = System.currentTimeMillis();
        long    millisAfter;
        do {
            startNanos = System.nanoTime();
            millisAfter = System.currentTimeMillis();
        } while ( millisAfter != millisBefore);
        firstNanoOrigin = ( long) ( millisAfter - ( startNanos / 1e6));
    }

    static NumberFormat lnf = DecimalFormat.getNumberInstance();
    static {
        lnf.setMaximumFractionDigits( 3);
        lnf.setGroupingUsed( true);
    };

    static class TimeCoherency {
        long    firstOrigin;
        long    lastOrigin;
        long    numMismatchToLast = 0;
        long    numMismatchToFirst = 0;
        long    numMismatchToFirstBig = 0;
        long    numChecks = 0;

        public TimeCoherency( long firstNanoOrigin) {
            firstOrigin = firstNanoOrigin;
            lastOrigin = firstOrigin;
        }
    }

    public static void main( String[] args) {
        Thread[]    threads = new Thread[ MAX_THREADS];
        for ( int i = 0;  i < MAX_THREADS;  i++) {
            final int   fi = i;
            final TimeCoherency tc = new TimeCoherency( firstNanoOrigin);
            threads[ i] = new Thread() {
                @Override
                public void run() {
                    long    start = getNow( tc);
                    long    firstOrigin = tc.lastOrigin;    // get the first origin for this thread
                    System.out.println( "Thread " + fi + " started at " + lnf.format( start) + " ns");
                    long    nruns = 0;
                    while ( getNow( tc) < RUNTIME_NS) {
                        nruns++;
                    }
                    final long  runTimeNS = getNow( tc) - start;
                    final long  originDrift = tc.lastOrigin - firstOrigin;
                    nruns += 3; // account for start and end call and the one that ends the loop
                    final long skipped = nruns - tc.numChecks;
                    System.out.println( "Thread " + fi + " finished after " + lnf.format( nruns) + " runs in " + lnf.format( runTimeNS) + " ns (" + lnf.format( ( double) runTimeNS / nruns) + " ns/call) with"
                            + "\n\t" + lnf.format( tc.numMismatchToFirst) + " different from first origin (" + lnf.format( 100.0 * tc.numMismatchToFirst / nruns) + "%)"
                            + "\n\t" + lnf.format( tc.numMismatchToLast) + " jumps from last origin (" + lnf.format( 100.0 * tc.numMismatchToLast / nruns) + "%)"
                            + "\n\t" + lnf.format( tc.numMismatchToFirstBig) + " different from first origin by more than " + BIG_OFFSET_MS + " ms"
                                    + " (" + lnf.format( 100.0 * tc.numMismatchToFirstBig / nruns) + "%)"
                            + "\n\t" + "total drift: " + lnf.format( originDrift) + " ms, " + lnf.format( skipped) + " skipped (" + lnf.format( 100.0 * skipped / nruns) + " %)");
                }};
            threads[ i].start();
        }
        try {
            for ( Thread thread : threads) {
                thread.join();
            }
        } catch ( InterruptedException ie) {};
    }

    public static long getNow( TimeCoherency coherency) {
        long    millisBefore = System.currentTimeMillis();
        long    now = System.nanoTime();
        if ( coherency != null) {
            checkOffset( now, millisBefore, coherency);
        }
        return now - startNanos;
    }

    private static void checkOffset( long nanoTime, long millisBefore, TimeCoherency tc) {
        long    millisAfter = System.currentTimeMillis();
        if ( millisBefore != millisAfter) {
            // disregard since thread may have slept between calls
            return;
        }
        tc.numChecks++;
        long    nanoMillis = ( long) ( nanoTime / 1e6);
        long    nanoOrigin = millisAfter - nanoMillis;
        long    oldOrigin = tc.lastOrigin;
        if ( oldOrigin != nanoOrigin) {
            tc.lastOrigin = nanoOrigin;
            tc.numMismatchToLast++;
        }
        if ( tc.firstOrigin != nanoOrigin) {
            tc.numMismatchToFirst++;
        }
        if ( Math.abs( tc.firstOrigin - nanoOrigin) > BIG_OFFSET_MS) {
            tc.numMismatchToFirstBig ++;
        }
    }
}

现在我做了一些小小的改变。基本上,我将nanoTime调用放在两个currentTimeMillis调用之间,以查看线程是否已被重新调度(这应该比currentTimeMillis解析更多)。在这种情况下,我忽略了循环循环。实际上,如果我们知道nanoTime足够快(就像在像常春藤桥这样的较新的体系结构上一样),我们可以在currentTimeMillis中使用nanoTime。

现在长到10毫秒的跳跃消失了。相反,当我们从每个线程的第一个源文件中得到超过2ms的值时,我们会进行计数。在我测试过的机器上,在100年的运行时,调用之间总是有接近200.000次的跳转。对于这些情况,我认为currentTimeMillis或nanoTime可能是不准确的。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-01-02 01:22:35

如前所述,每次计算一个新的原点意味着您会受到错误的影响。

代码语言:javascript
复制
//                               ______ delay _______
//                              v                    v
long origin = (long)(System.currentTimeMillis() - System.nanoTime() / 1e6);
//                                                                  ^
//                                                            truncation

如果你修改你的程序,所以你也计算原点的差异,你会发现它是非常小的。大约200 is的平均,我测量到,这是大约对时间延迟。

使用乘法而不是除法(再过几百年就可以避免溢出),您还会发现,计算出的不通过等式检查的起源数要大得多,约为99%。如果错误的原因是由于时间延迟,则只有当延迟恰好与最后一个相同时,它们才会通过。

一个简单得多的测试是在对nanoTime的一些后续调用中积累经过的时间,并查看它是否使用第一次和最后一次调用进行检查:

代码语言:javascript
复制
public class SimpleTimeCoherencyTest {
    public static void main(String[] args) {
        final long anchorNanos = System.nanoTime();

        long lastNanoTime = System.nanoTime();
        long accumulatedNanos = lastNanoTime - anchorNanos;

        long numCallsSinceAnchor = 1L;

        for(int i = 0; i < 100; i++) {
            TestRun testRun = new TestRun(accumulatedNanos, lastNanoTime);

            Thread t = new Thread(testRun);
            t.start();

            try {
                t.join();
            } catch(InterruptedException ie) {}

            lastNanoTime = testRun.lastNanoTime;
            accumulatedNanos = testRun.accumulatedNanos;
            numCallsSinceAnchor += testRun.numCallsToNanoTime;
        }

        System.out.println(numCallsSinceAnchor);
        System.out.println(accumulatedNanos);
        System.out.println(lastNanoTime - anchorNanos);
    }

    static class TestRun
    implements Runnable {
        volatile long accumulatedNanos;
        volatile long lastNanoTime;
        volatile long numCallsToNanoTime;

        TestRun(long acc, long last) {
            accumulatedNanos = acc;
            lastNanoTime = last;
        }

        @Override
        public void run() {
            long lastNanos = lastNanoTime;
            long currentNanos;

            do {
                currentNanos = System.nanoTime();
                accumulatedNanos += currentNanos - lastNanos;
                lastNanos = currentNanos;
                numCallsToNanoTime++;
            } while(currentNanos - lastNanoTime <= 100000000L);

            lastNanoTime = lastNanos;
        }
    }
}

该测试确实表明原点相同(或至少误差为零均值)。

票数 4
EN

Stack Overflow用户

发布于 2014-01-01 23:19:03

据我所知,System.currentTimeMillis()的方法有时确实会跳转,这取决于底层操作系统。我有时也观察到这种行为。

因此,您的代码给我的印象是,您试图得到System.nanoTime()System.currentTimeMillis()重复次数之间的偏移。您应该尝试通过只调用System.currentTimeMillis()一次来观察这一偏移量,然后才能说System.nanoTimes()有时会导致跳转。

顺便说一句,我不会假装规范(javadoc描述与某个不动点相关的System.nanoTime() )总是完美的实现。您可以查看这个讨论,其中多核CPU或CPU频率的变化会对System.nanoTime()所需的行为产生负面影响。但有一件事是肯定的。System.currentTimeMillis()更容易受到任意跳转的影响。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20874238

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档