我明白
there is overhead in setting up处理一个并行流,并且在单个线程中的处理速度更快,如果有少量项目或每个项目的处理速度很快.
但是,trySplit()
是否有类似的门槛,将问题分解成更小的块就会适得其反?我正在考虑类似于合并排序切换到最小块的插入排序.
如果是,阈值取决于trySplit()和consuming在tryAdvance()过程中的一个项目的相对成本?考虑一个拆分操作比推进一个数组索引分割一个有序的multiset排列要复杂得多.有没有一个惯例让客户在创建并行流时指定拆分的下限,这取决于消费者的复杂性? Spliterator可以用来估计下限本身的启发式?
或者,将Spliter的下限设置为1,总是安全的,让工作窃取算法考虑选择是否继续分割?
解决方法
一般来说,你不知道在尝试Advisor或forEachRemaining的消费者中做了多少工作.流管道和FJP都不知道这一点,因为它取决于用户提供的代码.它可以比分割程序快得多或慢得多.例如,您可能有两个元素的输入,但每个元素的处理需要一个小时,因此拆分此输入是非常合理的.
我通常尽可能多地分割输入.有三个技巧可以用来改善分裂:
>如果难以均匀分割,但您可以跟踪(或至少粗略估计)每个子部分的大小,可以自由分割.流的实现将在更大的部分进一步分裂.不要忘记SIZED和SUBSIZED的特点.
>将分割的重要部分移动到下一个tryAdvance / forEachRemaining调用.例如,假设你有一个已知的排列数,而在trySplit中,你将会跳到其他排列.这样的事情
public class MySpliterator implements Spliterator<String> { private long position; private String currentPermutation; private final long limit; MySpliterator(long position,long limit,String currentPermutation) { this.position = position; this.limit = limit; this.currentPermutation = currentPermutation; } @Override public Spliterator<String> trySplit() { if(limit - position <= 1) return null; long newPosition = (position+limit)>>>1; Spliterator<String> prefix = new MySpliterator(position,newPosition,currentPermutation); this.position = newPosition; this.currentPermutation = calculatePermutation(newPosition); // hard part return prefix; } ... }
将硬件部分移动到下一个tryAdvance调用,如下所示:
@Override public Spliterator<String> trySplit() { if(limit - position <= 1) return null; long newPosition = (position+limit)>>>1; Spliterator<String> prefix = new MySpliterator(position,currentPermutation); this.position = newPosition; this.currentPermutation = null; return prefix; } @Override public boolean tryAdvance(Consumer<? super String> action) { if(currentPermutation == null) currentPermutation = calculatePermutation(position); // hard part ... }
这样,硬件部分也将与前缀处理并行执行.>如果您目前没有这么多的元素(例如,不到10个),并且分配被要求,那么提升到一半的元素收集到数组中可能是很好的,然后创建一个基于数组的分割器对于这个前缀(类似于在AbstractSpliterator.trySplit()中完成).在这里,您可以控制所有代码,因此您可以提前测量trySplit的正常速度,而不是tryAdvance,并且在切换到基于阵列的拆分时估计阈值.