比较Collectors.summingLong和Collectors.counting

时间:2018-01-30 12:05:09

标签: java jvm jit jmh

基准测试在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.countingCollectors.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

我注意到的是:

branchesinstructionscycles差异近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

1 个答案:

答案 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,因此有大量的装箱和拆箱。