java - 进行提取方法重构后代码变慢了 6 倍
问题描述
我知道微基准测试很难。我不是想建立一个糟糕的微基准。相反,我在进行(我认为的)无害重构时遇到了这个问题。下面是该问题的精简演示。
该程序构建一万个随机整数的 ArrayList,然后找到元素的总和。在该示例中,求和被重复一百万次以提高经过时间测量中的信噪比。在实际程序中,有一百万个略有不同的列表,但无论如何都会产生问题。
App#arraySumInlined
是重构前的方法版本,总和保持在循环体中。App#arraySumSubFunctionCall
是将循环体提取到单独方法中的方法版本。
现在,(对我来说)令人惊讶的是,这arraySumInlined
需要约 7 秒,但arraySumSubFunctionCall
需要约 42 秒。在我看来,这似乎是一个足够令人印象深刻的差异。
如果我取消注释两者arraySumInlined
,arraySumSubFunctionCall
然后它们分别在约 7 秒内完成。即arraySumSubFunctionCall
不再那么慢。
这里发生了什么?有没有更广泛的影响?例如,我以前从未想过将提取方法重构为可以将 7 秒的方法调用转换为 42 秒的方法。
在研究这个时,我发现了几个涉及 JIT 的问题(例如Java 方法调用性能和为什么使用流的代码在 Java 9 中比 Java 8 运行得这么快?),但它们似乎处理相反的情况:内联代码性能更差而不是在单独的方法中编写代码。
环境详细信息: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)",
TimeUnit.NANOSECONDS.toMillis(end - start) / 1000.0, 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;
}
}
解决方案
对于它的价值,我也做了一些实验,发现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;
}
}
一旦我切换到getIntStream()
静态方法(在尝试其他排列之后),速度与内联执行时间相匹配。
推荐阅读
- java - 在 flink 1.13 中配置 RocksDB
- php - 为什么我的 POST 请求在提交表单后变成了 GET 请求?
- java - 尝试通过视图(小 v)标签包含 SurfaceView,但应用程序不断崩溃
- python - 如何使用另一个服务器目录上的 json 文件托管烧瓶或 Django 网站...其他服务器是不和谐机器人
- angular - 如何将从 qr 扫描对象获取的数据显示到浏览器
- sas - 在 SAS 中创建重复行并更改变量的值
- python - 数据质量 仅限数字列
- php - XMLHttpRequest() 有“此 XML 文件似乎没有任何与之关联的样式信息”错误
- c++ - 带有边界的拉普拉斯方程的 C++ 输出错误
- python - Pyomo:多维索引