测量时间间隔和乱序执行

5

我一直在阅读有关Java内存模型的内容,我知道编译器可以重新组织语句以优化代码。

假设我有以下代码:

long tick = System.nanoTime();
function_or_block_whose_time_i_intend_to_measure();
long tock = System.nanoTime();

编译器会不会在tick和tock之间重新组织代码,导致我想要测量的内容没有被执行?例如:

long tick = System.nanoTime();
long tock = System.nanoTime();
function_or_block_whose_time_i_intend_to_measure();

如果需要保持执行顺序,应该采取什么措施?
编辑: 通过nanoTime演示执行顺序混乱的示例:
public class Foo {
    public static void main(String[] args) {
        while (true) {
            long x = 0;

            long tick = System.nanoTime();
            for (int i = 0; i < 10000; i++) { // This for block takes ~15sec on my machine
                for (int j = 0; j < 600000; j++) {
                    x = x + x * x;
                }
            }

            long tock = System.nanoTime();
            System.out.println("time=" + (tock - tick));
            x = 0;
        }
    }
}

上述代码的输出结果为:
time=3185600
time=16176066510
time=16072426522
time=16297989268
time=16063363358
time=16101897865
time=16133391254
time=16170513289
time=16249963612
time=16263027561
time=16239506975

在上面的例子中,第一次迭代测量的时间明显低于随后运行时测量的时间。我认为这是由于乱序执行导致的。在第一次迭代中我做错了什么?


2
我的直觉是Java不会重新排列非内联函数调用,因为从普遍意义上来说,它无法知道它们可能具有什么副作用(特别是如果该函数在另一个编译单元中)。但我无法用任何东西来支持这一点。 - Patashu
这很可能是一个系统调用,超出了Java内存模型的范围。 - ZhongYu
2个回答

0
编译器会以一种使我想要测量的内容在tick和tock之间未被执行的方式重新组织代码吗?
不会。这将永远不会发生。如果编译器优化出现问题,那将是一个非常严重的错误。引用维基百科上的一句话:
“运行时(在这种情况下通常指动态编译器、处理器和内存子系统)可以自由地引入任何有用的执行优化,只要线程在独立环境中的结果保证与它在程序中出现的语句顺序(也称为程序顺序)下被执行的结果完全相同。”
因此,只要结果与按程序顺序执行时相同,就可以进行优化。在你所引述的情况下,我会假定优化是局部的,并且没有其他线程关心这些数据。这些优化旨在减少对主内存的访问次数,这可能是昂贵的。当涉及多个线程并且它们需要知道彼此状态时,您才会遇到这些优化的麻烦。

现在,如果两个线程需要始终看到彼此的状态,它们可以使用volatile变量或内存屏障(synchronized)来强制序列化对主内存的写入/读取。Infoq发表了一篇不错的文章,可能会引起您的兴趣。


0

Java 内存模型 (JMM) 定义了一个称为 happens-before 的部分顺序,用于程序中的所有操作。有七个规则定义了确保 happens-before 顺序的方法。其中之一被称为 程序顺序规则:

程序顺序规则。线程中的每个动作都发生在程序顺序中稍后出现的该线程中的每个动作之前。

根据此规则,编译器不会重新排序您的代码。

书籍 Java 并发实践 对此主题进行了很好的解释。


我仍然不理解。假设我有以下代码: long x, y; x = 10; // S2 y = 20; // S3 x = x + x; // S4 编译器最好能够优化代码,使S2和S4相继执行,然后再执行S3。这样做也是安全的,因为S3不依赖于S2或S4。通过这样做,S4中的计算可以在S2中x仍在寄存器中的情况下完成。我认为这就是乱序执行的全部意义。现在假设我用y = System.nanoTime();替换了S3。是否有一个“先行发生”规则来解释这个? - Abs

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