基准测试在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
慢了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次。还缓存操作。分支似乎很好地预测,也没有太多的缓存未命中(只有我的意见)。
所以问题可能在于编译后的代码。把它放在-prof perfasm
(太久不能把它放在这里)。
在编译的代码中,我注意到以下内容:
我。 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
第二次计算1次迭代的4个元素(该循环是否展开?)并且在第一次迭代后退出。
0x00007f9abd226ea6: add $0x1,%rsi
;...
0x00007f9abd226ed0: add $0x2,%rsi
;...
0x00007f9abd226efa: add $0x3,%rsi
;...
0x00007f9abd226f1c: add $0x4,%rbx
;...
0x00007f9abd226f20: mov %rbx,0x10(%r14)
第三个计算剩下的元素。
II 即可。 Collectors.counting
assembly
我们只有一个循环一个接一个地计算所有元素(未展开)。此外,我们在计数结果的循环内嵌入了拳击转换。此外,我们似乎没有在循环中内联拳击转换
0x00007f80dd22dfb5: mov $0x1,%esi
0x00007f80dd22dfba: nop
0x00007f80dd22dfbb: callq 0x7f80dd046420
似乎执行lambda 1L
中提供的e -> 1L
的装箱。但目前尚不清楚原因。在执行实际添加时,我们有以下代码:
0x00007f80dd22dfec: mov $0x1,%r10d
0x00007f80dd22dff2: add 0x10(%r11),%r10
此外,我们将计数结果存储在堆栈mov %r10d,0x10(%rsp)
内,而不是像第一种情况一样存储堆。
如果我对正在发生的事情的理解是正确的,我有
问题: 该循环是否因拳击转换而展开导致3倍减速?如果是这样,为什么运行时没有在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
答案 0 :(得分:8)
我没有看过集会或分析它,但看看源代码已经提供了一些信息。
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
,因此有大量的装箱和拆箱。