在多线程情况下使用限制流的最佳方式性能

Tah*_*kir 1 java random multithreading java-8 java-stream

我观看了JoséPaumard在InfoQ上的演讲:http: //www.infoq.com/fr/presentations/jdk8-lambdas-streams-collectors(法语)

问题是我被困在这一点上.要使用流多线程收集1M Long ,我们可以这样做:

Stream<Long> stream = 
  Stream.generate(() -> ThreadLocalRandom.current().nextLong()) ;

List<Long> list1 = 
  stream.parallel().limit(10_000_000).collect(Collectors.toList()) ;
Run Code Online (Sandbox Code Playgroud)

但考虑到线程总是在检查上述限制以阻碍性能.

在那次演讲中我们也看到了第二个解决方案:

Stream<Long> stream = 
  ThreadLocalRandom.current().longs(10_000_000).mapToObj(Long::new) ;

List<Long> list = 
  stream.parallel().collect(Collectors.toList()) ;
Run Code Online (Sandbox Code Playgroud)

它似乎是更好的表现.

所以这是我的问题:为什么第二个代码更好,是否有更好的,或者至少成本更低的方法呢?

Hol*_*ger 5

这是依赖实现的限制.关注并行性能的开发人员必须理解的一件事是,可预测的流大小通常有助于并行性能,因为它们允许平衡分配工作负载.

这里的问题是,无限流的组合通过创建Stream.generate()并且limit()不会产生具有可预测大小的流,尽管它看起来对我们来说是完全可预测的.

我们可以使用以下帮助器方法检查它:

static void sizeOf(String op, IntStream stream) {
    final Spliterator.OfInt s = stream.spliterator();
    System.out.printf("%-18s%5d, %d%n", op, s.getExactSizeIfKnown(), s.estimateSize());
}
Run Code Online (Sandbox Code Playgroud)

然后

sizeOf("randoms with size", ThreadLocalRandom.current().ints(1000));
sizeOf("randoms with limit", ThreadLocalRandom.current().ints().limit(1000));
sizeOf("range", IntStream.range(0, 100));
sizeOf("range map", IntStream.range(0, 100).map(i->i));
sizeOf("range filter", IntStream.range(0, 100).filter(i->true));
sizeOf("range limit", IntStream.range(0, 100).limit(10));
sizeOf("generate limit", IntStream.generate(()->42).limit(10));
Run Code Online (Sandbox Code Playgroud)

将打印

randoms with size  1000, 1000
randoms with limit   -1, 9223372036854775807
range               100, 100
range map           100, 100
range filter         -1, 100
range limit          -1, 100
generate limit       -1, 9223372036854775807
Run Code Online (Sandbox Code Playgroud)

所以我们看到,某些来源喜欢Random.ints(size)IntStream.range(…)产生具有可预测大小的流和某些中间操作,如map能够携带信息,因为他们知道大小不受影响.其他人喜欢filterlimit不传播大小(作为已知的确切大小).

很明显,filter无法预测元素的实际数量,但它提供了源大小作为估计,这是合理的,因为这是可以通过过滤器的元素的最大数量.

相比之下,当前limit实现不提供大小,即使源具有确切的大小,并且我们知道可预测的大小就这么简单min(source size, limit).相反,它甚至报告了一个荒谬的估计大小(来源的大小),尽管事实上已知结果大小永远不会高于限制.在无限流的情况下,我们有另外的障碍,Spliterator流所基于的接口没有办法报告它是无限的.在这些情况下,无限流+限制返回Long.MAX_VALUE作为估计,这意味着"我甚至无法猜测".

因此,根据经验,对于当前的实现,程序员应该避免limit在有预先在流的源处指定所需大小的方法时使用.但是,由于limit有序并行流的情况下(也不适用于random generate)也存在重大(记录)的缺点,因此大多数开发人员limit无论如何都要避免.

  • @Tagir Valeev:这个例子已经包含在我的答案中,参见`range limit`.我依稀记得一个讨论,`skip`和`limit`被认为比静态的,可预测的流大小更具动态性,但实际上,让这样的管道阶段执行`min`和调用之间没有区别`getExactSizeIfKnown`在任意`Spliterator`上.实际上,您可以使用自定义分裂器解决问题. (2认同)