为什么迭代速度会随着时间的推移而增加?[JAVA]



当我看到迭代速度不断提高时,我正在玩java中的循环。

看起来有点有趣。

有什么想法吗?

代码:

import org.junit.jupiter.api.Test;
public class RandomStuffTest {
public static long iterationsPerSecond = 0;
@Test
void testIterationSpeed() {
Thread t = new Thread(()->{
try{
while (true){
System.out.println("Iterations per second: "+iterationsPerSecond);
iterationsPerSecond = 0;
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
});
t.setDaemon(true);
t.start();
while (true){
for (long i = 0; i < Long.MAX_VALUE; i++) {
iterationsPerSecond++;
}
}
}
}

输出:

Iterations per second: 6111
Iterations per second: 2199824206
Iterations per second: 4539572003
Iterations per second: 6919540856
Iterations per second: 9442209284
Iterations per second: 11899448226
Iterations per second: 14313220638
Iterations per second: 16827637088
Iterations per second: 19322118707
Iterations per second: 21807781722
Iterations per second: 24256315314
Iterations per second: 26641505580

我注意到的另一件事:

CPU使用率一直在20%左右,并没有真正增加。。。

也许是因为我使用Junit将代码作为测试运行?

问题出在Java内存模型(JMM(上。

每个线程都可以拥有(不必这样做(每个字段的本地副本。无论何时写入或读取此字段,它都可以自由地设置其本地副本,并在很久以后将其与其他线程的本地副本同步。

换句话说,JVM可以自由地重新排序指令,并行地执行任务,或者应用它想要优化代码的任何奇怪的东西,只要某些保证永远不会被破坏。

一个易于理解的保证是:JVM可以自由地对2条顺序指令进行重新排序或并行化,,但除非通过计时,否则绝不可能编写能够观察到这一点的代码。

换句话说,int x = 0; x = 5; System.out.println(x);必须打印5,而决不能打印0。

您也可以在2个线程之间建立这样的关系,但这涉及到使用volatile和/或synchronized和/或在内部执行此操作的东西(java.util.concurrent包中的大多数东西(。

你没有,所以这个结果毫无意义。最有可能的是,指令iterationsPerSecond = 0没有效果;代码iterationsPerSecond++读取9442209284,递增1,然后将其写回-而该字段在所有这些操作中间的某个位置被写入0,因此什么都没有完成。

如果您想正确地测试这一点,请尝试volatile变量,或者更好的是AtomicLong

如前所述,代码由于数据竞争而中断。

由于数据竞赛,JIT可以对代码做一些有趣的事情:

while (true){
for (long i = 0; i < Long.MAX_VALUE; i++) {
iterationsPerSecond++;
}
}

由于它不知道另一个线程也在扰乱PerSecond的迭代,编译器可以折叠for循环,因为它可以计算循环的结果:

while (true){
iterationsPerSecond=Long.MAX_VALUE
}

它甚至可以决定退出循环的写入,因为写入了相同的值(循环不变代码运动(:

iterationsPerSecond=Long.MAX_VALUE
while (true){    
}

它甚至可以决定扔掉商店,因为它不知道有任何读者。因此,它实际上是一个死存储,因此它可以应用死代码消除。

while (true){    
}

原子或易失性可以解决问题,因为在边缘建立之前就会发生。使用volatile或atomiclong.get/set同样昂贵。它在硬件级别上有相同的编译器限制和围栏。

如果您想运行微基准测试,我建议您查看JMH。它会保护你免受许多琐碎错误的伤害。

最新更新