3

我在这里遇到了一个非常不寻常的问题。似乎调用 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() 来记录开始和结束时间。但是,正如您在上面注意到的那样,这效果不佳。

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

感谢您的任何意见!

4

3 回答 3

3

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

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

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

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

于 2015-11-15T10:59:42.450 回答
2

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

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

在这两种情况下,你都不能直接解决这个问题——我的意思是Thread.sleep也会影响你实际应用程序中的时间。但如果测量的有用功足够大,则误差可以忽略不计。

于 2015-11-15T11:13:12.727 回答
0

不一致可能不是来自 Java,而是来自不同的操作系统和虚拟机“原子”或系统时钟本身。

根据官方.nanoTime()文档:

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

资源

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

于 2015-11-15T05:16:00.337 回答