我没有将任何内容编译为 native,换句话说,我不使用来自 GraalVM 的
native-image
。我只是使用 GraalVM 运行相同的 Java 类(相同的 Java 字节码),然后使用常规 Oracle JVM 运行相同的 Java 类(相同的 Java 字节码)。
我使用哪个版本的 Java 或平台并不重要(我在 Linux 和 Mac 中进行了测试)。 GraalVM 始终比任何其他常规 JVM 快得多 (30 倍)。
看起来常规 JVM 没有使用 JIT 正确优化方法。请注意,该方法非常简单且很小。
有谁知道为什么会出现这种情况以及如何在常规 JVM 中解决这个问题?目前唯一的解决方法是迁移到 GraalVM。 编译并运行下面的代码来重现问题非常容易。只需先使用 Oracle JVM 进行编译并运行,然后再使用任何 Graal JVM 来查看差异。
谢谢!
public class OracleJvm23MathBug {
// simple and small amount of math
// =====> should be optimized/compiled/inlined for sure!
private static final long doSomething(int load, int i) {
long x = 0;
for (int j = 0; j < load; j++) {
long pow = (i % 8) * (i % 16);
if (i % 2 == 0) {
x += pow;
} else {
x -= pow;
}
}
return x;
}
/*
* Execute this with OpenJDK/Zulu/Oracle JVM 23 => average 215 nanoseconds
* Now execute this with Graal23 JVM 23 => average 7 nanoseconds
*
* This bug can be observed in any platform (I tested on Linux and Mac)
*
* $ java -version
* java version "23.0.1" 2024-10-15
* Java(TM) SE Runtime Environment (build 23.0.1+11-39)
* Java HotSpot(TM) 64-Bit Server VM (build 23.0.1+11-39, mixed mode, sharing)
*
* $ java -cp . OracleJvm23MathBug
* Value computed: -550000000000
* Measurements: 10000000| Avg Time: 215 nanos | Min Time: 83 nanos | Max Time: 199750 nanos
*
* $ java -version
* java version "23.0.1" 2024-10-15
* Java(TM) SE Runtime Environment Oracle GraalVM 23.0.1+11.1 (build 23.0.1+11-jvmci-b01)
* Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 23.0.1+11.1 (build 23.0.1+11-jvmci-b01, mixed mode, sharing)
*
* $ java -cp . OracleJvm23MathBug
* Value computed: -550000000000
* Measurements: 10000000| Avg Time: 7 nanos | Min Time: 0 nanos | Max Time: 178625 nanos
*/
public static final void main(String[] args) {
final int iterations = 10_000_000;
final int load = 10_000;
NanoBench bench = new NanoBench();
long computed = 0;
for (int i = 0; i < iterations; i++) {
bench.mark();
computed += doSomething(load, i);
bench.measure();
}
System.out.println("Value computed: " + computed);
bench.printResults();
}
private static class NanoBench {
private int measurements;
private long totalTime, minTime, maxTime, time;
private final StringBuilder sb = new StringBuilder(128);
NanoBench() {
reset();
}
public final void reset() {
totalTime = time = measurements = 0;
maxTime = Long.MIN_VALUE;
minTime = Long.MAX_VALUE;
}
public final void mark() {
time = System.nanoTime();
}
public final void measure() {
long lastNanoTime = System.nanoTime() - time;
totalTime += lastNanoTime;
minTime = lastNanoTime < minTime ? lastNanoTime : minTime;
maxTime = lastNanoTime > maxTime ? lastNanoTime : maxTime;
measurements++;
}
public final void printResults() {
sb.setLength(0);
sb.append("Measurements: ").append(measurements);
sb.append("| Avg Time: ").append((long) (totalTime / (double) measurements)).append(" nanos");
sb.append(" | Min Time: ").append(minTime).append(" nanos");
sb.append(" | Max Time: ").append(maxTime).append(" nanos\n\n");
for (int i = 0; i < sb.length(); i++) System.out.print(sb.charAt(i));
}
}
}
GraalVM 使用用 Java 编写的全新 JIT 实现。我不会说它比 HotSpot C2 JIT 编译器更好或更差,顺便说一下,它非常古老并且是用 C++ 编写的。正如 Adam Ruka 在这篇博文中所说:
大多数 JVM 发行版中使用的 JIT 编译器是 HotSpot。它是第一个 Java JIT 编译器,因此它的代码库相当古老。它也是用 C++ 编写的;由于 JIT 编译器是一项非常复杂的技术,C++ 的非托管性质意味着其代码中的每个潜在错误都可能导致非常严重的 JVM 问题,例如运行时崩溃、安全漏洞或内存泄漏。所有这些因素都意味着开发 HotSpot 非常困难,世界上只有少数专家能够真正做到这一点。这种缓慢的开发速度意味着 HotSpot 在支持所有最新优化方面落后于当前的技术水平。
但并非一切都失去了。您可以将 Graal JIT 编译器与最新的 Oracle JVM 23 结合使用:
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
如果您使用该选项运行代码,您会发现它运行得非常快。