Java 执行提取方法重构后,代码的速度会慢6倍

Java 执行提取方法重构后,代码的速度会慢6倍,java,performance,java-stream,refactoring,java-11,Java,Performance,Java Stream,Refactoring,Java 11,我知道微观基准测试很难。我不想建立一个糟糕的微观基准。相反,我在进行(我认为是)无害的重构时遇到了这个问题。下面是问题的详细演示 该程序构建一个包含一万个随机整数的数组列表,然后查找元素的和。在本例中,求和重复一百万次以改善信号v。测量经过时间时的噪声比。在真实的程序中,有一百万个稍有不同的列表,但问题的影响仍然存在 App#arraySumInlined是重构之前的方法版本,在循环体中保持内联求和 App#arraySumSubFunctionCall是将循环体提取到单独方法中的方法版本

我知道微观基准测试很难。我不想建立一个糟糕的微观基准。相反,我在进行(我认为是)无害的重构时遇到了这个问题。下面是问题的详细演示

该程序构建一个包含一万个随机整数的数组列表,然后查找元素的和。在本例中,求和重复一百万次以改善信号v。测量经过时间时的噪声比。在真实的程序中,有一百万个稍有不同的列表,但问题的影响仍然存在

  • App#arraySumInlined
    是重构之前的方法版本,在循环体中保持内联求和
  • App#arraySumSubFunctionCall
    是将循环体提取到单独方法中的方法版本
现在,(对我来说)令人惊讶的是
arraySumInlined
需要约7秒,而
arraySumSubFunctionCall
需要约42秒。在我看来,这是一个令人印象深刻的差异

如果我同时取消对
arraySumInlined
arraySumSubFunctionCall
的注释,则它们将在约7秒内完成。也就是说,
arraySumSubFunctionCall
不再那么慢了

这是怎么回事?是否有更广泛的影响?例如,我以前从未想过提取方法重构可以将7秒的方法调用转换为42秒的方法调用

在研究这一点时,我发现了几个涉及JIT的问题(例如和),但它们似乎处理的是相反的情况:内联代码的性能比单独方法中的代码差

环境详细信息:Windows 10 x64、Intel Core i3-6100

λ java -version
openjdk version "11.0.4" 2019-07-16
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.4+11)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.4+11, mixed mode)

λ javac -version
javac 11.0.4
import java.util.ArrayList;
导入java.util.Random;
导入java.util.concurrent.TimeUnit;
公共类应用程序{
公共静态void main(字符串[]args){
最终整型尺寸=10_000;
最终整数迭代=1_000_000;
最终var数据=带有随机值的整数列表(大小);
//阵列光照(迭代、数据);
arraySumSubFunctionCall(迭代、数据);
}
私有静态void arraySumSubFunctionCall(int迭代,
最终ArrayList数据){
最终长启动=System.nanoTime();
长结果=0;
对于(int i=0;ie.sum();
}
最终长端=System.nanoTime();
System.out.println(String.format(“%f秒(%d)”,
时间单位。纳秒。托米利(结束-开始)/1000.0,结果);
}
私有静态int getSum(最终ArrayList数据){
返回data.stream().mapToInt(e->e.sum();
}
私有静态ArrayList IntegerListWithRandomValue(最终整数大小){
最终var结果=新的ArrayList();
最终变量r=新随机变量();
对于(int i=0;i
我用你的代码做了一些实验,以下是我的结论:

1-如果先将arraySumSubFunctionCall()放在main()中,然后再将arraySumInlined()放在main()中,则执行时间将变为不同的时间:

public static void main(String[] args) {
    ...
    arraySumSubFunctionCall(iterations, data);
    arraySumInlined(iterations, data); 
}
这意味着JIT编译器优化在arraySumInlined()中进行,然后可以应用于arraySumSubFunctionCall()

2-如果在getSum()和arraySumInlined()中用一个真正动态的变量替换常量data.stream().mapToInt(e->e).sum(),如new Random().nextInt(),那么ArraySumSubfunction调用()和arraySumInlined()的执行时间将恢复到原来的水平

private static void arraySumInlined(int迭代,
最终ArrayList数据){
...
对于(int i=0;i
这意味着常量data.stream().mapToInt(e->e).sum()是在arraySumInlined()中优化的,然后应用于arraySumSubFunctionCall()


在现实生活中,我认为在一个局部for循环中重新计算N次相同的值并不经常发生,所以如果代码准备就绪需要,您不应该害怕提取方法重构。

为了它的价值,我也做了一些实验,发现它特别适用于
sum()
在静态方法内执行时,在IntStream上执行方法。我对您的代码进行了如下调整,以便获得每次迭代的平均持续时间:

import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class App2 {

    public static void main(String[] args) {
        final int size = 10_000;
        final int iterations = 1_000_000;
        final var data = integerListWithRandomValues(size);
        boolean inline = args.length > 0 && "inline".equalsIgnoreCase(args[0]);

        if (inline) {
            System.out.println("Running inline");
        } else {
            System.out.println("Running sub-function call");
        }

        arraySum(inline, iterations, data);
    }

    private static void arraySum(boolean inline, int iterations, final ArrayList<Integer> data) {
        long start;
        long result = 0;
        long totalElapsedTime = 0;

        for (int i = 0; i < iterations; ++i) {
            start = System.nanoTime();
            if (inline) {
                result = data.stream().mapToInt(e -> e).sum();
            } else {
                result = getIntStream(data).sum();
            }
            totalElapsedTime += getElapsedTime(start);
        }
        printElapsedTime(totalElapsedTime/iterations, result);
    }

    private static long getElapsedTime(long start) {
        return TimeUnit.NANOSECONDS.toNanos(System.nanoTime() - start);
    }

    private static void printElapsedTime(long elapsedTime, long result) {
        System.out.println(String.format("%d per iteration (%d)", elapsedTime, result));
    }

    private static IntStream getIntStream(final ArrayList<Integer> data) {
        return data.stream().mapToInt(e -> e);
    }

    private static int getSum(final ArrayList<Integer> data) {
        return data.stream().mapToInt(e -> e).sum();
    }

    private static ArrayList<Integer> integerListWithRandomValues(final int size) {
        final var result = new ArrayList<Integer>();
        final var r = new Random();

        for (int i = 0; i < size; ++i) {
            result.add(r.nextInt());
        }

        return result;
    }
}
import java.util.ArrayList;
导入java.util.Random;
导入java.util.concurrent.TimeUnit;
导入java.util.stream.IntStream;
公共类App2{
公共静态void main(字符串[]args){
最终整型尺寸=10_000;
最终整数迭代=1_000_000;
最终var数据=带有随机值的整数列表(大小);
布尔内联=args.length>0&“内联”.equalsIgnoreCase(args[0]);
如果(内联){
System.out.println(“运行内联”);
}否则{
System.out.println(“运行子函数调用”);
}
arraySum(内联、迭代、数据);
}
私有静态void arraySum(布尔内联、整数迭代、最终ArrayList数据){
长起点;
长结果=0;
long totalElapsedTime=0;
对于(int i=0;ie.sum();
}否则{
结果=getIntStream(数据).sum();
}
totalElapsedTime+=getElapsedTime(开始);
}
printElapsedTime(总eLapsedTime/ite
private static void arraySumInlined(int iterations,
      final ArrayList<Integer> data) {
    ...
    for (int i = 0; i < iterations; ++i) {
      result = new Random().nextInt();
    }
    ...
}


private static int getSum(final ArrayList<Integer> data) {
    return new Random().nextInt();
}
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class App2 {

    public static void main(String[] args) {
        final int size = 10_000;
        final int iterations = 1_000_000;
        final var data = integerListWithRandomValues(size);
        boolean inline = args.length > 0 && "inline".equalsIgnoreCase(args[0]);

        if (inline) {
            System.out.println("Running inline");
        } else {
            System.out.println("Running sub-function call");
        }

        arraySum(inline, iterations, data);
    }

    private static void arraySum(boolean inline, int iterations, final ArrayList<Integer> data) {
        long start;
        long result = 0;
        long totalElapsedTime = 0;

        for (int i = 0; i < iterations; ++i) {
            start = System.nanoTime();
            if (inline) {
                result = data.stream().mapToInt(e -> e).sum();
            } else {
                result = getIntStream(data).sum();
            }
            totalElapsedTime += getElapsedTime(start);
        }
        printElapsedTime(totalElapsedTime/iterations, result);
    }

    private static long getElapsedTime(long start) {
        return TimeUnit.NANOSECONDS.toNanos(System.nanoTime() - start);
    }

    private static void printElapsedTime(long elapsedTime, long result) {
        System.out.println(String.format("%d per iteration (%d)", elapsedTime, result));
    }

    private static IntStream getIntStream(final ArrayList<Integer> data) {
        return data.stream().mapToInt(e -> e);
    }

    private static int getSum(final ArrayList<Integer> data) {
        return data.stream().mapToInt(e -> e).sum();
    }

    private static ArrayList<Integer> integerListWithRandomValues(final int size) {
        final var result = new ArrayList<Integer>();
        final var r = new Random();

        for (int i = 0; i < size; ++i) {
            result.add(r.nextInt());
        }

        return result;
    }
}