我知道微基准测试很难。我并不是要建立一个糟糕的微型基准。相反,在进行(我认为是)无害重构时,我遇到了这个问题。下面是该问题的简化演示。
程序将构建一个包含一万个随机整数的ArrayList,然后查找元素的总和。在该示例中,将求和重复一百万次,以改善经过时间的测量中的信号与噪声比。在实际程序中,有一百万个略有不同的列表,但是不管怎样,问题的效果仍然成立。
-
App#arraySumInlined
是重构之前重构的方法版本,其求和保持在循环正文中。 -
App#arraySumSubFunctionCall
是将循环体提取到单独方法中的方法版本。
现在,令我惊讶的是,arraySumInlined
花费了约7秒,而arraySumSubFunctionCall
花费了约42秒。在我看来,这似乎是一个令人印象深刻的差异。
如果我取消对arraySumInlined
和arraySumSubFunctionCall
的评论,则它们将分别在约7秒内完成。即arraySumSubFunctionCall
不再那么慢。
这是怎么回事?有更广泛的含义吗?例如。我从来没有想过将提取方法重构看作可以将7秒方法调用变成42秒方法的事情。
在研究此问题时,我发现了几个涉及JIT的问题(例如Java method call performance和Why does this code using streams run so much faster in Java 9 than Java 8?),但它们似乎处理相反的情况:内联代码执行 worse 比代码在单独的方法中。
环境详细信息: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;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class App {
public static void main(String[] args) {
final int size = 10_000;
final int iterations = 1_000_000;
final var data = integerListWithRandomValues(size);
//arraySumInlined(iterations,data);
arraySumSubFunctionCall(iterations,data);
}
private static void arraySumSubFunctionCall(int iterations,final ArrayList<Integer> data) {
final long start = System.nanoTime();
long result = 0;
for (int i = 0; i < iterations; ++i) {
result = getSum(data);
}
final long end = System.nanoTime();
System.out.println(String.format("%f sec (%d)",TimeUnit.NANOSECONDS.toMillis(end - start) / 1000.0,result));
}
private static void arraySumInlined(int iterations,final ArrayList<Integer> data) {
final long start = System.nanoTime();
long result = 0;
for (int i = 0; i < iterations; ++i) {
result = data.stream().mapToInt(e -> e).sum();
}
final long end = System.nanoTime();
System.out.println(String.format("%f sec (%d)",result));
}
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;
}
}