EDIT2:由于更多细节而改写问题。
这让我很好奇。分析此代码:
public class ProfilerTest {
private static int loops = 1_000_000_000;
private static int listSize = 10;
public static void main(String[] args) {
for (int i = 0; i < loops; i++) {
var list = new ArrayList<Integer>(listSize);
for (int j = 0; j < listSize; j++) {
list.add(j);
}
assert list.size() == listSize;
}
}
}
Profiler 说
grow()
被 add(Object)
调用。查看源代码,我的 JDKs 实现 ArrayList.add(Object)
调用这个助手:
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
只有在
超出大小(
grow()
)时才应该调用s
。
即使我将
initialCapacity
增加到 2*listSize
,grow()
也会被调用。我在这里错过了什么?
我通过分析发现,我的一种方法有时会调用
ArrayList.grow()
。问题是,该方法根本不应该调用 grow()
,因为我知道所需的大小。不过,可以在其他方法中修改大小。
对此感到惊讶,我做了一点基准测试。 结果对我来说似乎很奇怪。
MatchingCapacity
我的问题:
grow()
,或者这可能只是由于探查器不准确(即子例程调用grow()
但有时最终会在父调用者中进行测量?)initialCapacity
在基准测试中变慢,它是否以某种方式破坏了 Java 编译器优化?不幸的是,我在尝试分析 JMH 基准以获得更多见解时遇到错误..
编辑:正如评论中指出的那样,假设我看的是时间而不是吞吐量,我读的数字都是错误的,所以剩下的问题是关于分析器的。
@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 1, time = 3)
@Measurement(iterations = 3, time = 3)
@Fork(value=2)
public class PerformanceTest {
private int size = 30;
@Benchmark
public ArrayList<Integer> MatchingCapacity() {
var list = new ArrayList<Integer>(size);
for (int i = 0; i < size; i++) {
list.add(i);
}
return list;
}
@Benchmark
public ArrayList<Integer> MismatchingCapacity() {
var list = new ArrayList<Integer>(size-1);
for (int i = 0; i < size; i++) {
list.add(i);
}
return list;
}
@Benchmark
public ArrayList<Integer> OversizedCapacity() {
var list = new ArrayList<Integer>(size + size/2);
for (int i = 0; i < size; i++) {
list.add(i);
}
return list;
}
@Benchmark
public ArrayList<Integer> DefaultCapacity() {
var list = new ArrayList<Integer>();
for (int i = 0; i < size; i++) {
list.add(i);
}
return list;
}
}
# JMH version: 1.35
# VM version: JDK 17.0.3.1, Java HotSpot(TM) 64-Bit Server VM, 17.0.3.1+2-LTS-6
size=3
PerformanceTest.DefaultCapacity thrpt 6 37681589.763 � 10696982.725 ops/s
PerformanceTest.MatchingCapacity thrpt 6 54285199.864 � 20674575.714 ops/s
PerformanceTest.MismatchingCapacity thrpt 6 21430358.674 � 11202223.852 ops/s
PerformanceTest.OversizedCapacity thrpt 6 50519838.911 � 19575866.550 ops/s
size=30
PerformanceTest.DefaultCapacity thrpt 6 4253362.573 � 120027.530 ops/s
PerformanceTest.MatchingCapacity thrpt 6 8071248.285 � 5138133.875 ops/s
PerformanceTest.MismatchingCapacity thrpt 6 5108706.433 � 562235.033 ops/s
PerformanceTest.OversizedCapacity thrpt 6 6867041.239 � 5561825.025 ops/s
size=300
PerformanceTest.DefaultCapacity thrpt 6 466885.040 � 486083.752 ops/s
PerformanceTest.MatchingCapacity thrpt 6 693234.753 � 268499.890 ops/s
PerformanceTest.MismatchingCapacity thrpt 6 644182.306 � 324785.510 ops/s
PerformanceTest.OversizedCapacity thrpt 6 773775.315 � 26808.161 ops/s
size=3000
PerformanceTest.DefaultCapacity thrpt 6 58673.411 � 23803.125 ops/s
PerformanceTest.MatchingCapacity thrpt 6 64863.127 � 33194.262 ops/s
PerformanceTest.MismatchingCapacity thrpt 6 58921.951 � 21346.733 ops/s
PerformanceTest.OversizedCapacity thrpt 6 62658.432 � 32061.866 ops/s
没有。方法
ArrayList.grow(int)
在需要时被调用。
但是,您的代码并不是 ArrayList 的唯一用途。在运行 main 方法之前,类加载器会运行。作为他们实施的一部分,他们正在使用 ArrayList。
您可以在调试器中看到这种情况。在 main() 方法的第一条语句上设置断点。当它命中时,然后在
ArrayList.grow(int)
中设置一个断点。您的代码不会命中该断点。