【问题标题】:Accuracy of System.nanoTime() to measure time elapsed decreases after a call to Thread.sleep()调用 Thread.sleep() 后,System.nanoTime() 测量经过时间的准确性降低
【发布时间】:2015-11-15 04:18:20
【问题描述】:

我在这里遇到了一个非常不寻常的问题。似乎调用 Thread.sleep(n),其中 n > 0 会导致以下 System.nanoTime() 调用难以预测。

下面的代码演示了这个问题。

在我的电脑上运行它(rMBP 15" 2015, OS X 10.11, jre 1.8.0_40-b26)输出如下结果:

Control: 48497
Random: 36719
Thread.sleep(0): 48044
Thread.sleep(1): 832271

在运行 Windows 8 的虚拟机上(VMware Horizo​​n、Windows 8.1 为 1.8.0_60-b27):

Control: 98974
Random: 61019
Thread.sleep(0): 115623
Thread.sleep(1): 282451

但是,在企业服务器(VMware、RHEL 6.7、jre 1.6.0_45-b06)上运行它:

Control: 1385670
Random: 1202695
Thread.sleep(0): 1393994
Thread.sleep(1): 1413220

这出乎我的意料。

显然 Thread.sleep(1) 会影响以下代码的计算。我不知道为什么会这样。有人知道吗?

谢谢!

public class Main {
    public static void main(String[] args) {
        int N = 1000;
        long timeElapsed = 0;
        long startTime, endTime = 0;

        for (int i = 0; i < N; i++) {
            startTime = System.nanoTime();
            //search runs here
            endTime = System.nanoTime();

            timeElapsed += endTime - startTime;
        }

        System.out.println("Control: " + timeElapsed);

        timeElapsed = 0;

        for (int i = 0; i < N; i++) {
            startTime = System.nanoTime();
            //search runs here
            endTime = System.nanoTime();

            timeElapsed += endTime - startTime;

            for (int j = 0; j < N; j++) {
                int k = (int) Math.pow(i, j);
            }
        }

        System.out.println("Random: " + timeElapsed);

        timeElapsed = 0;

        for (int i = 0; i < N; i++) {
            startTime = System.nanoTime();
            //search runs here
            endTime = System.nanoTime();

            timeElapsed += endTime - startTime;

            try {
                Thread.sleep(0);
            } catch (InterruptedException e) {
                break;
            }
        }

        System.out.println("Thread.sleep(0): " + timeElapsed);

        timeElapsed = 0;

        for (int i = 0; i < N; i++) {
            startTime = System.nanoTime();
            //search runs here
            endTime = System.nanoTime();

            timeElapsed += endTime - startTime;

            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                break;
            }
        }

        System.out.println("Thread.sleep(1): " + timeElapsed);
    }
}

基本上,我在 while 循环中运行搜索,每次迭代都会通过调用 Thread.sleep() 来中断。我想从运行搜索的总时间中排除睡眠时间,所以我使用 System.nanoTime() 来记录开始和结束时间。但是,正如您在上面所注意到的,这并不适用。

有没有办法解决这个问题?

感谢您的任何意见!

【问题讨论】:

  • 嗨@TheLima,我明白你的意思。这只是我所面临问题的任意演示。您可以在两者之间放置任意数量的代码,您仍然会注意到相同的结果:Thread.sleep() 确实增加了两次 System.nanoTime() 调用之间的时间
  • 您的代码在解释帧中执行。你真的想测量解释器的速度吗?将整个 main 正文移动到单独的方法中,并在循环中调用它 100 次。后续调用的结果将大不相同。
  • 我已经编辑了 OP 以添加与正在执行的操作相关的重要注释;因为没有它们,可能会有,并且有曾经,混乱............并移动了startTime和@ 987654328@ 变量退出循环,因为进行这么多不必要的分配应该被视为大罪。 =P
  • 感谢您的编辑。尽管通常最好将变量保持在适当的范围内。例如,在这种情况下,startTime 和 endTime 不需要超过它们包含的循环。 @TagirValeev,我不太明白你在暗示什么。代码,因为人们误解了(我认为从我的代码中抽象出潜在的问题会有所帮助_向上here
  • @TheLima 正如我所说的那样,作为对您现在已删除的答案的反对理由,将变量声明移出循环字面上没有区别。这些是原始变量,没有对象分配;即使它们是非原始的,当您分配一个新值时,您仍然会分配一个新对象。如果您查看带有在循环内部和外部声明的变量的字节码,它将是相同的。 “大罪”是在没有证据的情况下以优化的名义进行更改。

标签: java performance nanotime


【解决方案1】:

这是一个复杂的话题,因为 JVM 使用的计时器高度依赖于 CPU 和操作系统,并且还会随着 JVM 版本而变化(例如,通过使用更新的操作系统 API)。虚拟机还可能会限制它们传递给来宾的 CPU 功能,与裸机设置相比,这可能会改变选择。

在 x86 上,RDTSC 指令提供所有时钟的最低延迟和最佳粒度,但在某些配置下,它作为时间源不可用或不够可靠。

在 linux 上,您应该检查内核启动消息 (dmesg)、与 tsc 相关的 /proc/cpuinfo 标志和选定的 /sys/devices/system/clocksource/*/current_clocksource。默认情况下,内核会尝试使用 TSC,如果没有,可能是有原因的。

对于一些历史,您可能想阅读以下内容,但请注意,其中一些文章可能有点过时了,TSC 的可靠性多年来有了很大的提高:

【讨论】:

    【解决方案2】:

    我可以提出这种行为的至少两个可能原因:

    1. 省电。 执行繁忙循环时,CPU 以最高性能状态运行。但是,在Thread.sleep 之后,它很可能落入power-saving states 之一,频率和电压降低。之后 CPU 不会立即恢复到其最大性能,这可能需要几纳秒到几微秒。
    2. 调度。线程因Thread.sleep而被取消调度后,将在发生与System.nanoTime所用定时器相关的定时器事件可能后再次被调度执行.

    在这两种情况下,您都无法直接解决此问题 - 我的意思是 Thread.sleep 也会影响您实际应用程序中的时间安排。但如果测量的有用工作量足够大,则误差可以忽略不计。

    【讨论】:

    • 需要注意的是:英特尔的速度步骤可能需要数百毫秒。英特尔正在开发一种新的更快的速度步骤,以在几毫秒内改变频率。
    【解决方案3】:

    不一致可能不是来自 Java,而是来自不同的操作系统和 VM“atomic-” 或系统时钟本身。

    根据.nanoTime()官方文档:

    不做任何保证,除非分辨率至少一样好 和 currentTimeMillis() 一样

    source

    ...根据个人知识,这是因为在某些操作系统和虚拟机中,系统本身不支持 "atomic" 时钟,这对于更高分辨率是必需的。 (一旦我再次找到它,我将发布链接以获取此信息......已经很长时间了。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-09-26
      • 2018-07-03
      相关资源
      最近更新 更多