比较 Collectors.summingLong 和 Collectors.counting 的性能
Comparing performance of Collectors.summingLong and Collectors.counting
基准 运行 低于 intel core i5, Ubuntu
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
我正在比较 Collectors.counting
和 Collectors.summingLong(x -> 1L)
的性能。这是基准:
public List<Integer> ints = new ArrayList<>();
Collector<Integer, ?, Long> counting = Collectors.counting();
Collector<Integer, ?, Long> summingLong = Collectors.summingLong(x -> 1L);
@Setup
public void setup() throws Exception{
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
}
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public Long counting() {
return ints.stream().collect(counting);
}
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public Long summingLong() {
return ints.stream().collect(summingLong);
}
我得到的结果是 Collectors.counting
慢了 3 倍 Collectors.summingLong
。
所以我 运行 它与 -prof perfnorm
和 25 个叉子。这是结果:
Benchmark Mode Cnt Score Error Units
MyBenchmark.counting avgt 125 87.115 ± 3.787 ns/op
MyBenchmark.counting:CPI avgt 25 0.310 ± 0.011 #/op
MyBenchmark.counting:L1-dcache-load-misses avgt 25 1.963 ± 0.171 #/op
MyBenchmark.counting:L1-dcache-loads avgt 25 258.716 ± 41.458 #/op
MyBenchmark.counting:L1-dcache-store-misses avgt 25 1.890 ± 0.168 #/op
MyBenchmark.counting:L1-dcache-stores avgt 25 131.344 ± 16.433 #/op
MyBenchmark.counting:L1-icache-load-misses avgt 25 0.035 ± 0.007 #/op
MyBenchmark.counting:LLC-loads avgt 25 0.411 ± 0.143 #/op
MyBenchmark.counting:LLC-stores avgt 25 0.007 ± 0.001 #/op
MyBenchmark.counting:branch-misses avgt 25 0.029 ± 0.017 #/op
MyBenchmark.counting:branches avgt 25 139.669 ± 21.901 #/op
MyBenchmark.counting:cycles avgt 25 261.967 ± 29.392 #/op
MyBenchmark.counting:dTLB-load-misses avgt 25 0.036 ± 0.003 #/op
MyBenchmark.counting:dTLB-loads avgt 25 258.111 ± 41.008 #/op
MyBenchmark.counting:dTLB-store-misses avgt 25 0.001 ± 0.001 #/op
MyBenchmark.counting:dTLB-stores avgt 25 131.394 ± 16.166 #/op
MyBenchmark.counting:iTLB-load-misses avgt 25 0.001 ± 0.001 #/op
MyBenchmark.counting:iTLB-loads avgt 25 0.001 ± 0.001 #/op
MyBenchmark.counting:instructions avgt 25 850.262 ± 113.228 #/op
MyBenchmark.counting:stalled-cycles-frontend avgt 25 48.493 ± 8.968 #/op
MyBenchmark.summingLong avgt 125 37.238 ± 0.194 ns/op
MyBenchmark.summingLong:CPI avgt 25 0.311 ± 0.002 #/op
MyBenchmark.summingLong:L1-dcache-load-misses avgt 25 1.793 ± 0.013 #/op
MyBenchmark.summingLong:L1-dcache-loads avgt 25 93.785 ± 0.640 #/op
MyBenchmark.summingLong:L1-dcache-store-misses avgt 25 1.727 ± 0.013 #/op
MyBenchmark.summingLong:L1-dcache-stores avgt 25 56.249 ± 0.408 #/op
MyBenchmark.summingLong:L1-icache-load-misses avgt 25 0.020 ± 0.003 #/op
MyBenchmark.summingLong:LLC-loads avgt 25 0.843 ± 0.117 #/op
MyBenchmark.summingLong:LLC-stores avgt 25 0.004 ± 0.001 #/op
MyBenchmark.summingLong:branch-misses avgt 25 0.008 ± 0.002 #/op
MyBenchmark.summingLong:branches avgt 25 61.472 ± 0.260 #/op
MyBenchmark.summingLong:cycles avgt 25 110.949 ± 0.784 #/op
MyBenchmark.summingLong:dTLB-load-misses avgt 25 0.031 ± 0.001 #/op
MyBenchmark.summingLong:dTLB-loads avgt 25 93.662 ± 0.616 #/op
MyBenchmark.summingLong:dTLB-store-misses avgt 25 ≈ 10⁻³ #/op
MyBenchmark.summingLong:dTLB-stores avgt 25 56.302 ± 0.351 #/op
MyBenchmark.summingLong:iTLB-load-misses avgt 25 0.001 ± 0.001 #/op
MyBenchmark.summingLong:iTLB-loads avgt 25 ≈ 10⁻³ #/op
MyBenchmark.summingLong:instructions avgt 25 357.029 ± 1.712 #/op
MyBenchmark.summingLong:stalled-cycles-frontend avgt 25 10.074 ± 1.096 #/op
我注意到的是:
branches
、instructions
、cycles
相差近3倍。还有缓存操作。 B运行ches 似乎预测得很好,也没有太多缓存未命中(仅我的意见)。
所以问题可能出在编译代码上。 运行 它和 -prof perfasm
(太长了放在这里)。
在编译后的代码中,我注意到以下内容:
I. Collectors.summingLong
assembly
我们有 3 个循环遍历数组并计数。首先只计算一个元素
0x00007f9abd226dfd: mov %edi,%ebp ;contains the iteration index
incl %ebp
;...
0x00007f9abd226e27: incl %edi
0x00007f9abd226e29: cmp %ebp,%edi
0x00007f9abd226e2b: jnl 0x7f9abd226e34 ;exit after the first iteration
第二次计算 4 个元素进行 1 次迭代(循环展开了吗?)并且在第一个之后退出。
0x00007f9abd226ea6: add [=16=]x1,%rsi
;...
0x00007f9abd226ed0: add [=16=]x2,%rsi
;...
0x00007f9abd226efa: add [=16=]x3,%rsi
;...
0x00007f9abd226f1c: add [=16=]x4,%rbx
;...
0x00007f9abd226f20: mov %rbx,0x10(%r14)
第三个计算其余元素。
II。 Collectors.counting
assembly
我们只有一个循环来逐一计算所有元素(未展开)。我们还在计数结果的循环内进行了内联装箱转换。此外,我们似乎还没有在循环内进行内联装箱转换
0x00007f80dd22dfb5: mov [=17=]x1,%esi
0x00007f80dd22dfba: nop
0x00007f80dd22dfbb: callq 0x7f80dd046420
似乎执行 lambda e -> 1L
中提供的 1L
的装箱。但这并不清楚为什么。在执行实际添加时,我们有以下代码:
0x00007f80dd22dfec: mov [=18=]x1,%r10d
0x00007f80dd22dff2: add 0x10(%r11),%r10
此外,我们将计数结果存储在堆栈中mov %r10d,0x10(%rsp)
,而不是像第一种情况那样存储在堆中。
如果我对正在发生的事情的理解是正确的,我有
问题: 循环展开和装箱转换是否导致了 3 倍的减速?如果是这样,为什么 运行time 没有展开 counting
案例中的循环?
注意 Collectors.summingLong
的 GC 压力比 Collectors.counting
高 2.5。这不是很清楚(我只能猜测我们将中间值存储在 Collectors.counting
的堆栈中)。
MyBenchmark.counting avgt 5 96.956 ± 4.412 ns/op
MyBenchmark.counting:·gc.alloc.rate avgt 5 734.538 ± 33.083 MB/sec
MyBenchmark.counting:·gc.alloc.rate.norm avgt 5 112.000 ± 0.001 B/op
MyBenchmark.counting:·gc.churn.PS_Eden_Space avgt 5 731.423 ± 340.767 MB/sec
MyBenchmark.counting:·gc.churn.PS_Eden_Space.norm avgt 5 111.451 ± 48.411 B/op
MyBenchmark.counting:·gc.churn.PS_Survivor_Space avgt 5 0.017 ± 0.067 MB/sec
MyBenchmark.counting:·gc.churn.PS_Survivor_Space.norm avgt 5 0.003 ± 0.010 B/op
MyBenchmark.counting:·gc.count avgt 5 16.000 counts
MyBenchmark.counting:·gc.time avgt 5 12.000 ms
MyBenchmark.summingLong avgt 5 38.371 ± 1.733 ns/op
MyBenchmark.summingLong:·gc.alloc.rate avgt 5 1856.581 ± 81.706 MB/sec
MyBenchmark.summingLong:·gc.alloc.rate.norm avgt 5 112.000 ± 0.001 B/op
MyBenchmark.summingLong:·gc.churn.PS_Eden_Space avgt 5 1876.736 ± 192.503 MB/sec
MyBenchmark.summingLong:·gc.churn.PS_Eden_Space.norm avgt 5 113.213 ± 9.916 B/op
MyBenchmark.summingLong:·gc.churn.PS_Survivor_Space avgt 5 0.033 ± 0.072 MB/sec
MyBenchmark.summingLong:·gc.churn.PS_Survivor_Space.norm avgt 5 0.002 ± 0.004 B/op
MyBenchmark.summingLong:·gc.count avgt 5 62.000 counts
MyBenchmark.summingLong:·gc.time avgt 5 48.000 ms
我没有查看程序集或对其进行分析,但查看源代码已经提供了一些信息。
summingLong()
导致此收集器:
new CollectorImpl<>(
() -> new long[1],
(a, t) -> { a[0] += mapper.applyAsLong(t); },
(a, b) -> { a[0] += b[0]; return a; },
a -> a[0], CH_NOID);
counting()
结果是:
new CollectorImpl<>(
boxSupplier(identity),
(a, t) -> { a[0] = op.apply(a[0], mapper.apply(t)); },
(a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },
a -> a[0], CH_NOID);
如您所见,counting()
版本做了更多的事情:
- 拳击
- 调用
op.apply(...)
由于 op
是 Long::sum
,它对基元进行操作,因此涉及大量装箱和拆箱操作。
基准 运行 低于 intel core i5, Ubuntu
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
我正在比较 Collectors.counting
和 Collectors.summingLong(x -> 1L)
的性能。这是基准:
public List<Integer> ints = new ArrayList<>();
Collector<Integer, ?, Long> counting = Collectors.counting();
Collector<Integer, ?, Long> summingLong = Collectors.summingLong(x -> 1L);
@Setup
public void setup() throws Exception{
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
ints.add(new Random().nextInt(1000));
}
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public Long counting() {
return ints.stream().collect(counting);
}
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public Long summingLong() {
return ints.stream().collect(summingLong);
}
我得到的结果是 Collectors.counting
慢了 3 倍 Collectors.summingLong
。
所以我 运行 它与 -prof perfnorm
和 25 个叉子。这是结果:
Benchmark Mode Cnt Score Error Units
MyBenchmark.counting avgt 125 87.115 ± 3.787 ns/op
MyBenchmark.counting:CPI avgt 25 0.310 ± 0.011 #/op
MyBenchmark.counting:L1-dcache-load-misses avgt 25 1.963 ± 0.171 #/op
MyBenchmark.counting:L1-dcache-loads avgt 25 258.716 ± 41.458 #/op
MyBenchmark.counting:L1-dcache-store-misses avgt 25 1.890 ± 0.168 #/op
MyBenchmark.counting:L1-dcache-stores avgt 25 131.344 ± 16.433 #/op
MyBenchmark.counting:L1-icache-load-misses avgt 25 0.035 ± 0.007 #/op
MyBenchmark.counting:LLC-loads avgt 25 0.411 ± 0.143 #/op
MyBenchmark.counting:LLC-stores avgt 25 0.007 ± 0.001 #/op
MyBenchmark.counting:branch-misses avgt 25 0.029 ± 0.017 #/op
MyBenchmark.counting:branches avgt 25 139.669 ± 21.901 #/op
MyBenchmark.counting:cycles avgt 25 261.967 ± 29.392 #/op
MyBenchmark.counting:dTLB-load-misses avgt 25 0.036 ± 0.003 #/op
MyBenchmark.counting:dTLB-loads avgt 25 258.111 ± 41.008 #/op
MyBenchmark.counting:dTLB-store-misses avgt 25 0.001 ± 0.001 #/op
MyBenchmark.counting:dTLB-stores avgt 25 131.394 ± 16.166 #/op
MyBenchmark.counting:iTLB-load-misses avgt 25 0.001 ± 0.001 #/op
MyBenchmark.counting:iTLB-loads avgt 25 0.001 ± 0.001 #/op
MyBenchmark.counting:instructions avgt 25 850.262 ± 113.228 #/op
MyBenchmark.counting:stalled-cycles-frontend avgt 25 48.493 ± 8.968 #/op
MyBenchmark.summingLong avgt 125 37.238 ± 0.194 ns/op
MyBenchmark.summingLong:CPI avgt 25 0.311 ± 0.002 #/op
MyBenchmark.summingLong:L1-dcache-load-misses avgt 25 1.793 ± 0.013 #/op
MyBenchmark.summingLong:L1-dcache-loads avgt 25 93.785 ± 0.640 #/op
MyBenchmark.summingLong:L1-dcache-store-misses avgt 25 1.727 ± 0.013 #/op
MyBenchmark.summingLong:L1-dcache-stores avgt 25 56.249 ± 0.408 #/op
MyBenchmark.summingLong:L1-icache-load-misses avgt 25 0.020 ± 0.003 #/op
MyBenchmark.summingLong:LLC-loads avgt 25 0.843 ± 0.117 #/op
MyBenchmark.summingLong:LLC-stores avgt 25 0.004 ± 0.001 #/op
MyBenchmark.summingLong:branch-misses avgt 25 0.008 ± 0.002 #/op
MyBenchmark.summingLong:branches avgt 25 61.472 ± 0.260 #/op
MyBenchmark.summingLong:cycles avgt 25 110.949 ± 0.784 #/op
MyBenchmark.summingLong:dTLB-load-misses avgt 25 0.031 ± 0.001 #/op
MyBenchmark.summingLong:dTLB-loads avgt 25 93.662 ± 0.616 #/op
MyBenchmark.summingLong:dTLB-store-misses avgt 25 ≈ 10⁻³ #/op
MyBenchmark.summingLong:dTLB-stores avgt 25 56.302 ± 0.351 #/op
MyBenchmark.summingLong:iTLB-load-misses avgt 25 0.001 ± 0.001 #/op
MyBenchmark.summingLong:iTLB-loads avgt 25 ≈ 10⁻³ #/op
MyBenchmark.summingLong:instructions avgt 25 357.029 ± 1.712 #/op
MyBenchmark.summingLong:stalled-cycles-frontend avgt 25 10.074 ± 1.096 #/op
我注意到的是:
branches
、instructions
、cycles
相差近3倍。还有缓存操作。 B运行ches 似乎预测得很好,也没有太多缓存未命中(仅我的意见)。
所以问题可能出在编译代码上。 运行 它和 -prof perfasm
(太长了放在这里)。
在编译后的代码中,我注意到以下内容:
I. Collectors.summingLong
assembly
我们有 3 个循环遍历数组并计数。首先只计算一个元素
0x00007f9abd226dfd: mov %edi,%ebp ;contains the iteration index
incl %ebp
;...
0x00007f9abd226e27: incl %edi
0x00007f9abd226e29: cmp %ebp,%edi
0x00007f9abd226e2b: jnl 0x7f9abd226e34 ;exit after the first iteration
第二次计算 4 个元素进行 1 次迭代(循环展开了吗?)并且在第一个之后退出。
0x00007f9abd226ea6: add [=16=]x1,%rsi
;...
0x00007f9abd226ed0: add [=16=]x2,%rsi
;...
0x00007f9abd226efa: add [=16=]x3,%rsi
;...
0x00007f9abd226f1c: add [=16=]x4,%rbx
;...
0x00007f9abd226f20: mov %rbx,0x10(%r14)
第三个计算其余元素。
II。 Collectors.counting
assembly
我们只有一个循环来逐一计算所有元素(未展开)。我们还在计数结果的循环内进行了内联装箱转换。此外,我们似乎还没有在循环内进行内联装箱转换
0x00007f80dd22dfb5: mov [=17=]x1,%esi
0x00007f80dd22dfba: nop
0x00007f80dd22dfbb: callq 0x7f80dd046420
似乎执行 lambda e -> 1L
中提供的 1L
的装箱。但这并不清楚为什么。在执行实际添加时,我们有以下代码:
0x00007f80dd22dfec: mov [=18=]x1,%r10d
0x00007f80dd22dff2: add 0x10(%r11),%r10
此外,我们将计数结果存储在堆栈中mov %r10d,0x10(%rsp)
,而不是像第一种情况那样存储在堆中。
如果我对正在发生的事情的理解是正确的,我有
问题: 循环展开和装箱转换是否导致了 3 倍的减速?如果是这样,为什么 运行time 没有展开 counting
案例中的循环?
注意 Collectors.summingLong
的 GC 压力比 Collectors.counting
高 2.5。这不是很清楚(我只能猜测我们将中间值存储在 Collectors.counting
的堆栈中)。
MyBenchmark.counting avgt 5 96.956 ± 4.412 ns/op
MyBenchmark.counting:·gc.alloc.rate avgt 5 734.538 ± 33.083 MB/sec
MyBenchmark.counting:·gc.alloc.rate.norm avgt 5 112.000 ± 0.001 B/op
MyBenchmark.counting:·gc.churn.PS_Eden_Space avgt 5 731.423 ± 340.767 MB/sec
MyBenchmark.counting:·gc.churn.PS_Eden_Space.norm avgt 5 111.451 ± 48.411 B/op
MyBenchmark.counting:·gc.churn.PS_Survivor_Space avgt 5 0.017 ± 0.067 MB/sec
MyBenchmark.counting:·gc.churn.PS_Survivor_Space.norm avgt 5 0.003 ± 0.010 B/op
MyBenchmark.counting:·gc.count avgt 5 16.000 counts
MyBenchmark.counting:·gc.time avgt 5 12.000 ms
MyBenchmark.summingLong avgt 5 38.371 ± 1.733 ns/op
MyBenchmark.summingLong:·gc.alloc.rate avgt 5 1856.581 ± 81.706 MB/sec
MyBenchmark.summingLong:·gc.alloc.rate.norm avgt 5 112.000 ± 0.001 B/op
MyBenchmark.summingLong:·gc.churn.PS_Eden_Space avgt 5 1876.736 ± 192.503 MB/sec
MyBenchmark.summingLong:·gc.churn.PS_Eden_Space.norm avgt 5 113.213 ± 9.916 B/op
MyBenchmark.summingLong:·gc.churn.PS_Survivor_Space avgt 5 0.033 ± 0.072 MB/sec
MyBenchmark.summingLong:·gc.churn.PS_Survivor_Space.norm avgt 5 0.002 ± 0.004 B/op
MyBenchmark.summingLong:·gc.count avgt 5 62.000 counts
MyBenchmark.summingLong:·gc.time avgt 5 48.000 ms
我没有查看程序集或对其进行分析,但查看源代码已经提供了一些信息。
summingLong()
导致此收集器:
new CollectorImpl<>(
() -> new long[1],
(a, t) -> { a[0] += mapper.applyAsLong(t); },
(a, b) -> { a[0] += b[0]; return a; },
a -> a[0], CH_NOID);
counting()
结果是:
new CollectorImpl<>(
boxSupplier(identity),
(a, t) -> { a[0] = op.apply(a[0], mapper.apply(t)); },
(a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },
a -> a[0], CH_NOID);
如您所见,counting()
版本做了更多的事情:
- 拳击
- 调用
op.apply(...)
由于 op
是 Long::sum
,它对基元进行操作,因此涉及大量装箱和拆箱操作。