当JVM不执行代码优化的方法时
When JVM does not execute method for code optimization
我正在阅读 Scott Oaks 的书 "Java Performance" 并遇到一段代码,据说 Java 7 或 8 JVM 足够聪明,可以跳过 for 循环中提供的计算部分,因为结果是将来不会使用(微基准测试)。
书中提到的代码:
public void doTest() {
// Main Loop
double l;
long then = System.currentTimeMillis();
for (int i = 0; i < nLoops; i++) {
l = fibImpl1(50);
}
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
private double fibImpl1(int n) {
if (n < 0) throw new IllegalArgumentException("Must be > 0");
if (n == 0) return 0d;
if (n == 1) return 1d;
double d = fibImpl1(n - 2) + fibImpl(n - 1);
if (Double.isInfinite(d)) throw new ArithmeticException("Overflow");
return d;
}
书中的进一步陈述:
因为 Fibonacci 计算的结果从不使用,所以编译器可以随意丢弃该计算。智能编译器(包括当前 Java 7 和 8 编译器)将最终执行此代码:
long then = System.currentTimeMillis();
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
为了验证这一点,我尝试了一个代码,但经过的时间计算并没有反映上面解释的理论。
我的代码版本:
public class APSum {
public static void main(String[] args) {
long then = System.currentTimeMillis();
ArithmeticProgression.sum(500000000);
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
}
class ArithmeticProgression{
public static double sum(int i){
double sum=0;
for(int index=0; index<=i; index++){
sum = sum + (double)index;
}
return sum;
}
}
所以请让我知道如何实现书中提到的场景。还是JVM希望JVM要不要优化调用?
现代 JVM 太复杂了,需要进行各种优化。如果您尝试测量一小段代码,如果不非常非常 详细了解 JVM 正在做什么,要正确地完成它真的很复杂。死代码消除 (DCE) 是一种经常导致微基准测试出错的优化。
有两个经典错误(阅读更多常见错误http://shipilev.net/talks/jvmls-July2014-benchmarking.pdf and ):
- 由于编译的最小单位是方法,benchmark必须有多个main方法。
- 没有预热迭代。始终包括一个预热阶段,该阶段会一直运行您的测试内核,足以在计时阶段之前触发所有初始化和编译。
更正后我们的基准如下所示:
public class APSum {
public static void main(String[] args) {
for (int i = 0; i < 5000; i++) {
test();
}
}
private static void test() {
long then = System.currentTimeMillis();
sum(5000000);
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
public static double sum(int i){
double sum=0;
for(int index=0; index<=i; index++){
sum = sum + (double)index;
}
return sum;
}
}
在此示例中,DCE 仅在内联之后发生。让我们从内联树开始(-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -XX:-BackgroundCompilation
可用)
@ 0 java.lang.System::currentTimeMillis (0 bytes) intrinsic
@ 6 edu.jvm.runtime.APSum::sum (22 bytes) inlining prohibited by policy
@ 10 java.lang.System::currentTimeMillis (0 bytes) intrinsic
编译器未能内联方法 APSum::sum,因为 OSR. In fact, though OSR compilation 在基准测试中频繁触发(尤其是在微基准测试中),它在应用程序代码中触发的频率较低。为了获得正确的结果,我们必须添加更多的预热迭代:
public class APSum {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
test();
}
}
private static void test() {
long then = System.currentTimeMillis();
sum(5000000);
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
public static double sum(int i){
double sum=0;
for(int index=0; index<=i; index++){
sum = sum + (double)index;
}
return sum;
}
}
作为迭代结束时的结果,我们有:
Elapsed time: 5
Elapsed time: 4
Elapsed time: 5
@ 0 java.lang.System::currentTimeMillis (0 bytes) (intrinsic)
@ 6 edu.jvm.runtime.APSum::sum (22 bytes) inline (hot)
@ 10 java.lang.System::currentTimeMillis (0 bytes) (intrinsic)
Elapsed time: 0
Elapsed time: 0
Elapsed time: 0
我正在阅读 Scott Oaks 的书 "Java Performance" 并遇到一段代码,据说 Java 7 或 8 JVM 足够聪明,可以跳过 for 循环中提供的计算部分,因为结果是将来不会使用(微基准测试)。
书中提到的代码:
public void doTest() {
// Main Loop
double l;
long then = System.currentTimeMillis();
for (int i = 0; i < nLoops; i++) {
l = fibImpl1(50);
}
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
private double fibImpl1(int n) {
if (n < 0) throw new IllegalArgumentException("Must be > 0");
if (n == 0) return 0d;
if (n == 1) return 1d;
double d = fibImpl1(n - 2) + fibImpl(n - 1);
if (Double.isInfinite(d)) throw new ArithmeticException("Overflow");
return d;
}
书中的进一步陈述: 因为 Fibonacci 计算的结果从不使用,所以编译器可以随意丢弃该计算。智能编译器(包括当前 Java 7 和 8 编译器)将最终执行此代码:
long then = System.currentTimeMillis();
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
为了验证这一点,我尝试了一个代码,但经过的时间计算并没有反映上面解释的理论。
我的代码版本:
public class APSum {
public static void main(String[] args) {
long then = System.currentTimeMillis();
ArithmeticProgression.sum(500000000);
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
}
class ArithmeticProgression{
public static double sum(int i){
double sum=0;
for(int index=0; index<=i; index++){
sum = sum + (double)index;
}
return sum;
}
}
所以请让我知道如何实现书中提到的场景。还是JVM希望JVM要不要优化调用?
现代 JVM 太复杂了,需要进行各种优化。如果您尝试测量一小段代码,如果不非常非常 详细了解 JVM 正在做什么,要正确地完成它真的很复杂。死代码消除 (DCE) 是一种经常导致微基准测试出错的优化。
有两个经典错误(阅读更多常见错误http://shipilev.net/talks/jvmls-July2014-benchmarking.pdf and ):
- 由于编译的最小单位是方法,benchmark必须有多个main方法。
- 没有预热迭代。始终包括一个预热阶段,该阶段会一直运行您的测试内核,足以在计时阶段之前触发所有初始化和编译。
更正后我们的基准如下所示:
public class APSum {
public static void main(String[] args) {
for (int i = 0; i < 5000; i++) {
test();
}
}
private static void test() {
long then = System.currentTimeMillis();
sum(5000000);
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
public static double sum(int i){
double sum=0;
for(int index=0; index<=i; index++){
sum = sum + (double)index;
}
return sum;
}
}
在此示例中,DCE 仅在内联之后发生。让我们从内联树开始(-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -XX:-BackgroundCompilation
@ 0 java.lang.System::currentTimeMillis (0 bytes) intrinsic
@ 6 edu.jvm.runtime.APSum::sum (22 bytes) inlining prohibited by policy
@ 10 java.lang.System::currentTimeMillis (0 bytes) intrinsic
编译器未能内联方法 APSum::sum,因为 OSR. In fact, though OSR compilation 在基准测试中频繁触发(尤其是在微基准测试中),它在应用程序代码中触发的频率较低。为了获得正确的结果,我们必须添加更多的预热迭代:
public class APSum {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
test();
}
}
private static void test() {
long then = System.currentTimeMillis();
sum(5000000);
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
public static double sum(int i){
double sum=0;
for(int index=0; index<=i; index++){
sum = sum + (double)index;
}
return sum;
}
}
作为迭代结束时的结果,我们有:
Elapsed time: 5
Elapsed time: 4
Elapsed time: 5
@ 0 java.lang.System::currentTimeMillis (0 bytes) (intrinsic)
@ 6 edu.jvm.runtime.APSum::sum (22 bytes) inline (hot)
@ 10 java.lang.System::currentTimeMillis (0 bytes) (intrinsic)
Elapsed time: 0
Elapsed time: 0
Elapsed time: 0