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