微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

JMH - List#addAll 比 ArrayList#new 快吗?

如何解决JMH - List#addAll 比 ArrayList#new 快吗?

我有一个非常简单的 JMH 基准测试,我已经从左到右读到使用集合的构造函数应该比使用 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<String>

@State(Scope.Benchmark)
public static class Strings {
    public String string = "String";
    public List<String> strings = Arrays.asList("String123","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

解决方法

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

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

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。