将一个spliterator拆分成N个spliterator
Split a spliterator into N spliterators
在分析一位同事几年前的 Java7 代码时,我发现他实现了一个可能并行遍历数据的实用程序。他称之为Range
,它扩展了Iterator
接口。它的一些新方法令人尴尬地熟悉:
int size()
会给出范围的确切大小;
Range split()
会将范围分成 2 部分,但最好是大小相似(修改当前范围并创建新范围);
Range[] split(int n)
会将范围分成 N 个子范围,可能试图使它们尽可能均匀。
- 虽然
void remove()
是从 Iterator
开始的,但它的子类型只是抛出 UnsupportedOperationException
。
我对自己说的是,这绝对是我们现在可以用(小型)分离器做的,从而利用流 API。但是,Java 8 中的拆分器没有提供拆分成 n
部分的简单方法。
使用递归函数,我们可以将其拆分为具有 L
递归级别的 n = 2^L
部分,但是当 n
不是 2 的幂时,该方法并不那么简单,更不用说仅仅为了效果而保留效用函数感觉不自然了。
有人可能还会说简单地避免乱用拆分器,让流在实际处理过程中进行分叉引起的拆分,但 ForkJoin 策略可能不太适合这项任务,并且不能保证它将使用我们特别希望专用于该作业的线程数。事实上,可能存在对少量元素执行繁重任务的情况。
问题总结如下:有一个至少具有 SIZED 和 SUBSIZED 特征的拆分器,我怎样才能将它拆分成准确数量的拆分器?
实现它的方法是编写一个拆分器 wrapper,它使用自己的拆分政策。您失去的是利用本机随机访问结构的能力;您只能通过迭代内部 splitetator 的数据集来拆分。
您可以参考我的earlier answer,我在其中展示了分成预定大小的批次的代码;只需调用适当的构造函数,就可以很容易地使其适应您的情况。
“… but the ForkJoin strategy might not be very adequate for the task”
对我来说听起来像是过早的优化。您想手动实施复杂的事情,因为现有的策略 可能 不合适…
“… and does not guarantee that it will use the number of threads that we specifically wish to dedicate to the job.”
确实没有保证,但当前的流实现使用 common Fork/Join pool which 。将指定数量的线程专用于任务正是您所要求的策略。
“There could be cases of heavy tasks being performed on a small number of elements, in fact.”
我认为 F/J 框架的工作方式没有任何矛盾。它将尝试拆分,直到达到所需的并行度。如果这意味着每个线程只处理一个项目,那么它就可以了。
在这一点上,值得注意的是,与内核数量相匹配的默认并行度足以满足任何不涉及阻塞的计算任务,无论处理单个项目需要多少时间。只要每个线程都有它的工作量,就不可能有超出实际硬件执行单元数的加速。
换句话说,F/J 框架实现了你想要自己实现的策略(或者比你要实现的策略更好的策略),这让我们回到了第一点,过早的优化。
在分析一位同事几年前的 Java7 代码时,我发现他实现了一个可能并行遍历数据的实用程序。他称之为Range
,它扩展了Iterator
接口。它的一些新方法令人尴尬地熟悉:
int size()
会给出范围的确切大小;Range split()
会将范围分成 2 部分,但最好是大小相似(修改当前范围并创建新范围);Range[] split(int n)
会将范围分成 N 个子范围,可能试图使它们尽可能均匀。- 虽然
void remove()
是从Iterator
开始的,但它的子类型只是抛出UnsupportedOperationException
。
我对自己说的是,这绝对是我们现在可以用(小型)分离器做的,从而利用流 API。但是,Java 8 中的拆分器没有提供拆分成 n
部分的简单方法。
使用递归函数,我们可以将其拆分为具有 L
递归级别的 n = 2^L
部分,但是当 n
不是 2 的幂时,该方法并不那么简单,更不用说仅仅为了效果而保留效用函数感觉不自然了。
有人可能还会说简单地避免乱用拆分器,让流在实际处理过程中进行分叉引起的拆分,但 ForkJoin 策略可能不太适合这项任务,并且不能保证它将使用我们特别希望专用于该作业的线程数。事实上,可能存在对少量元素执行繁重任务的情况。
问题总结如下:有一个至少具有 SIZED 和 SUBSIZED 特征的拆分器,我怎样才能将它拆分成准确数量的拆分器?
实现它的方法是编写一个拆分器 wrapper,它使用自己的拆分政策。您失去的是利用本机随机访问结构的能力;您只能通过迭代内部 splitetator 的数据集来拆分。
您可以参考我的earlier answer,我在其中展示了分成预定大小的批次的代码;只需调用适当的构造函数,就可以很容易地使其适应您的情况。
“… but the ForkJoin strategy might not be very adequate for the task”
对我来说听起来像是过早的优化。您想手动实施复杂的事情,因为现有的策略 可能 不合适…
“… and does not guarantee that it will use the number of threads that we specifically wish to dedicate to the job.”
确实没有保证,但当前的流实现使用 common Fork/Join pool which
“There could be cases of heavy tasks being performed on a small number of elements, in fact.”
我认为 F/J 框架的工作方式没有任何矛盾。它将尝试拆分,直到达到所需的并行度。如果这意味着每个线程只处理一个项目,那么它就可以了。
在这一点上,值得注意的是,与内核数量相匹配的默认并行度足以满足任何不涉及阻塞的计算任务,无论处理单个项目需要多少时间。只要每个线程都有它的工作量,就不可能有超出实际硬件执行单元数的加速。
换句话说,F/J 框架实现了你想要自己实现的策略(或者比你要实现的策略更好的策略),这让我们回到了第一点,过早的优化。