【问题标题】:JMH - List#addAll faster than ArrayList#new?JMH - List#addAll 比 ArrayList#new 快吗?
【发布时间】:2021-05-31 10:24:28
【问题描述】:

我有一个相当简单的 JMH Benchmark,我左右阅读过,使用集合的构造函数应该比使用 addAll 方法更快。

然而,我的基准往往证明相反。

对此有何解释?

@State(Scope.Benchmark)
public static class Strings {
    public String string = "String";
    public List<String> strings = Arrays.asList("String123", "String456", "String789");
}

@Benchmark
@Fork(value = 5, warmups = 3)
public List<String> withStreams(Strings input) {
    return Stream.concat(Stream.of(input.string), input.strings.stream())
            .collect(Collectors.toList());
}

@Benchmark
@Fork(value = 5, warmups = 3)
public List<String> withoutStreamsButWithConstructor(Strings input) {
    List<String> result = new ArrayList<>(input.strings);
    result.add(input.string);
    return result;
}

@Benchmark
@Fork(value = 5, warmups = 3)
public List<String> withoutStreams(Strings input) {
    List<String> result = new ArrayList<>();
    result.add(input.string);
    result.addAll(input.strings);
    return result;
}

PS:Streams 的示例只是一个实验(实际上是我想确定的实际情况)

结果

# Run complete. Total time: 00:16:31

Benchmark                              Mode  Cnt         Score        Error  Units
App.withStreams                       thrpt  100  12649053,043 ± 222716,712  ops/s
App.withoutStreams                    thrpt  100  50572729,531 ± 324271,706  ops/s
App.withoutStreamsButWithConstructor  thrpt  100  30179733,201 ± 380273,095  ops/s

更新

为家庭添加了以下基准

@Benchmark
@Fork(value = 5, warmups = 3)
public List<String> withoutStreamsWithAddAfter(Strings input) {
    List<String> result = new ArrayList<>();
    result.addAll(input.strings);
    result.add(input.string);
    return result;
}

我现在明白了

# Run complete. Total time: 00:22:00

Benchmark                              Mode  Cnt         Score        Error  Units
App.withStreams                       thrpt  100  13560464,180 ± 201012,539  ops/s
App.withoutStreams                    thrpt  100  47490197,224 ± 864545,886  ops/s
App.withoutStreamsButWithConstructor  thrpt  100  29412182,733 ± 346228,939  ops/s
App.withoutStreamsWithAddAfter        thrpt  100  31030909,677 ±  81494,995  ops/s

所以withoutStreams 是性能最好的一个


更新 #2

我已尝试使用以下List&lt;String&gt;

@State(Scope.Benchmark)
public static class Strings {
    public String string = "String";
    public List<String> strings = Arrays.asList("String123", "String456", "String789", "StringAbc", "StringDef", "StringGhi", "StringJkl", "StringMno", "StringPqr", "StringStu");
}

现在结果对于构造函数@Benchmark确实更好

# Run complete. Total time: 00:22:01

Benchmark                              Mode  Cnt         Score        Error  Units
App.withStreams                       thrpt  100   7291967,397 ± 330614,125  ops/s
App.withoutStreams                    thrpt  100  23575768,665 ± 127039,282  ops/s
App.withoutStreamsButWithConstructor  thrpt  100  27046342,511 ± 182227,005  ops/s
App.withoutStreamsWithAddAfter        thrpt  100  17873682,945 ± 170786,259  ops/s

【问题讨论】:

  • 顺序重要吗?首先添加string 或首先添加strings - 应该在所有睾丸中保持相同的顺序 - 实际上为什么单个string
  • 没有订单不会,但我可以用 add after 重新运行另一个订单

标签: java performance arraylist collections jmh


【解决方案1】:

我怀疑原因在于 ArrayList 的实现方式。

withoutStreamsButWithConstructor 创建一个大小为 1 的数组,当您调用 addAll 时,会创建一个大小为 4 的新数组,复制第一个元素,然后添加其他元素。

withoutStreams 创建一个大小为 10(默认容量)的数组,无需调整大小。

如果strings 包含 10 个元素,则两种方法都需要调整数组大小,我怀疑结果会更接近。

更一般地说,如果性能很重要,则使用采用int 的构造函数正确调整列表大小会有所不同。

【讨论】:

  • 谢谢@assylias 我会马上用strings中的10个元素试试
  • 请注意,这并不能反驳使用构造函数比 addAll 快的说法,因为基准测试没有将构造函数与 addAll 进行比较。对于固定的输入大小和 Java 版本,它将“默认构造函数,添加,添加所有”与“复制构造函数,添加”进行比较。
猜你喜欢
  • 1970-01-01
  • 2011-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-22
  • 2017-05-08
  • 2012-09-29
  • 2012-04-08
相关资源
最近更新 更多