本以为直接创建会更快,但事实上,添加循环只需要一半的时间。到底发生了什么事情速度变慢了?
这是测试代码
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class Test_newArray {
private static int num = 10000;
private static int length = 10;
@Benchmark
public static int[][] newArray() {
return new int[num][length];
}
@Benchmark
public static int[][] newArray2() {
int[][] temps = new int[num][];
for (int i = 0; i < temps.length; i++) {
temps[i] = new int[length];
}
return temps;
}
}
测试结果如下。
Benchmark Mode Cnt Score Error Units
Test_newArray.newArray avgt 25 289.254 ± 4.982 us/op
Test_newArray.newArray2 avgt 25 114.364 ± 1.446 us/op
测试环境如下
JMH版本:1.21
虚拟机版本:JDK 1.8.0_212、OpenJDK 64 位服务器虚拟机、25.212-b04
最佳答案
在 Java 中,有一个单独的字节码指令用于分配多维数组 - multianewarray
.
newArray
基准测试使用multianewarray
字节码;newArray2
在循环中调用简单的newarray
。
问题在于 HotSpot JVM 没有用于 multianewarray
字节码的快速路径*。该指令始终在VM运行时执行。因此,分配不会内联在编译的代码中。
第一个基准测试必须付出 Java 和 VM 运行时上下文之间切换的性能损失。此外,VM 运行时中的通用分配代码(用 C++ 编写)并不像 JIT 编译代码中的内联分配那样优化,只是因为它是通用的,即没有针对特定对象类型或对象进行优化对于特定的调用站点,它执行额外的运行时检查等。
以下是使用 async-profiler 分析两个基准的结果。我使用的是 JDK 11.0.4,但对于 JDK 8,图片看起来类似。
第一种情况,99%的时间都花在OptoRuntime::multianewarray2_C
里面- VM运行时中的C++代码。
在第二种情况下,图表的大部分是绿色的,这意味着程序主要在 Java 上下文中运行,实际上执行专门针对给定基准优化的 JIT 编译代码。
编辑
* 实际上,HotSpot JVM 可以内联 multianewarray
,但前提是分配总数不超过 -XX: MultiArrayExpandLimit
默认为 6。
因此,例如,new int[5][10]
将在JIT编译的代码中内联分配,而new int[10][5]
的分配code> 将通过 VM 运行时。
关于java - 为什么分配单个 2D 数组比循环分配多个总大小和形状相同的 1D 数组花费的时间更长?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58158445/