Thread.sleep()调用后,使用System.nanoTime()测量时间经过的精度会降低。

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 Horizon,在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()调用之间的时间。 - Kheng Boon Pek
您的代码在解释框架中执行。您真的想要测量解释器速度吗?将整个“main”主体移动到单独的方法中,并在循环中调用它100次。连续调用的结果将会有显著不同。 - Tagir Valeev
我已经编辑了原始帖子,添加了与正在进行的工作相关的重要说明;因为没有这些说明,可能会产生混淆,而且确实发生了混淆............并且将startTimeendTime变量移出循环,因为进行那么多不必要的分配应该被视为一种大罪。=P - CosmicGiant
@TheLima如果你发表评论指责原帖作者犯了“大罪”,并且还厚颜无耻地编辑了原帖的代码(不仅是微调或格式化,而是实际上更改了它),那么完全可以公开批评你。 - Andy Turner
@AndyTurner 首先,我确实检查了OP的代码;其次,即使只是一个注释说明他的搜索在哪里执行(我的编辑),OP的代码也缺少关键信息。第三,任何重要的改进都是可以接受的,而“更改”OP的代码,而不是“修复”它通常是不好的。第四,这个问题是由他的代码中的这个问题造成的误解,在OP中已经被修复,并被我删除,所以这里有致命的问题:你到底是在“呼叫我出来”做什么?这会解决什么问题?它将帮助谁?你只是生气吗? - CosmicGiant
显示剩余3条评论
3个回答

3
这是一个复杂的话题,因为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 之后,它很可能会进入其中一种省电状态,频率和电压降低。在此之后,CPU 不会立即返回到其最大性能状态,这可能需要几纳秒到微秒级别的时间。
  2. 调度。 在由于 Thread.sleep 而取消调度线程之后,该线程将在计时器事件之后再次被调度执行,该事件与用于 System.nanoTime 的计时器可能有关
在这两种情况下,您无法直接解决这个问题 - 我的意思是 Thread.sleep 也会影响您真实应用程序中的时间。但是,如果测量到的有用工作量足够大,则不准确性将是可以忽略不计的。

需要注意的是:英特尔的速度步进可能需要数百毫秒。英特尔正在研发一种新的更快速的速度步进,以在几毫秒内改变频率。 - Jire

0

这些不一致可能不是由Java引起的,而是由不同的操作系统和VMs中的"原子"或系统时钟本身引起的。

根据官方的.nanoTime()文档:

除了保证分辨率至少与currentTimeMillis()相同之外,不做任何保证

来源

...我可以从个人知识中得知,在某些操作系统和VMs中,系统本身不支持"原子"时钟,这对于更高的分辨率是必要的。(我会尽快再次找到这些信息的来源链接...已经过了很长时间。)


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