为什么ArrayList.add()会调用ArrayList.grow()而不超过ArrayList.size()?

问题描述 投票:0回答:1

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
不仅表现更差,而且它的可变性也更高,至少对于小尺寸而言,在我的用例中,这是最常见的。

我的问题:

  • 当您将 ArrayList 填充到其确切数量时,是否真的有可能 java 调用
    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
java performance arraylist benchmarking jmh
1个回答
0
投票

没有。方法

ArrayList.grow(int)
在需要时被调用。

但是,您的代码并不是 ArrayList 的唯一用途。在运行 main 方法之前,类加载器会运行。作为他们实施的一部分,他们正在使用 ArrayList。

您可以在调试器中看到这种情况。在 main() 方法的第一条语句上设置断点。当它命中时,然后在

ArrayList.grow(int)
中设置一个断点。您的代码不会命中该断点。

© www.soinside.com 2019 - 2024. All rights reserved.