JMH - List#addAll 比 ArrayList#new 更快?
JMH - List#addAll faster than 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", "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
我怀疑原因在于 ArrayList 的实现方式。
withoutStreamsButWithConstructor
创建一个大小为 1 的数组,当您调用 addAll
时,会创建一个大小为 4 的新数组,第一个元素被复制过来,然后添加其他元素。
withoutStreams
创建一个大小为 10(默认容量)的数组,不需要调整大小。
如果 strings
包含 10 个元素,则两种方法都需要调整一次数组大小,我怀疑结果会更接近。
更一般地说,如果性能很重要,使用采用 int
的构造函数正确调整列表大小可能会有所不同。
我有一个非常简单的 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", "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
我怀疑原因在于 ArrayList 的实现方式。
withoutStreamsButWithConstructor
创建一个大小为 1 的数组,当您调用 addAll
时,会创建一个大小为 4 的新数组,第一个元素被复制过来,然后添加其他元素。
withoutStreams
创建一个大小为 10(默认容量)的数组,不需要调整大小。
如果 strings
包含 10 个元素,则两种方法都需要调整一次数组大小,我怀疑结果会更接近。
更一般地说,如果性能很重要,使用采用 int
的构造函数正确调整列表大小可能会有所不同。