在几年前分析同事的Java 7代码时,我发现他实现了一个可能并行处理数据的实用程序。他称之为Range
,并扩展了Iterator
界面。它的一些新方法很难熟悉:
int size()
会给出范围的确切大小; Range split()
会将范围分为2个部分,最好是,但不一定,大小相似(修改当前范围并创建新范围); Range[] split(int n)
会将范围分为N个子范围,可能会尝试使其尽可能均匀。void remove()
来自Iterator
,但其子类型只是投掷UnsupportedOperationException
。我对自己说的是,这绝对是我们现在可以用(子化)分裂器做的事情,因此利用了流API。但是,Java 8中的分裂器并没有提供一种简单的方法来分割成n
部分。
使用递归函数,我们可以将其分解为具有n = 2^L
递归级别的L
部分,但是当n
不是2的幂时,该方法并不是那么简单,不是提到保持效用函数只是为了效果感觉不自然。
有人可能会说,简单地避免搞乱分裂器并让一个流在实际处理期间执行由fork引起的分裂,但是ForkJoin策略可能不适合该任务,并且不保证它将使用我们特别希望专注于工作的线程数。事实上,可能存在对少数元素执行繁重任务的情况。
问题总结如下:如果分裂器至少具有SIZED和SUBSIZED的特征,我该如何将其拆分为精确数量的分裂器?
答案 0 :(得分:2)
使用自己的拆分策略编写分裂器包装器的方法,即使是现有的F / J支持框架,也不是那种hacky和互操作。您失去的是利用本机随机访问结构的能力;你只能通过迭代内部分割器的数据集进行拆分。
您可以参考我的earlier answer,其中我提供的代码分为预定大小的批次;只需适当的构造函数调用就可以很容易地使它适应你的情况。
答案 1 :(得分:2)
“......但是ForkJoin战略可能不适合任务”
听起来像是对我不成熟的优化。您希望手动实现复杂的事物,因为现有的策略可能不合适......
“...并不保证它会使用我们特别希望专注于该作业的线程数。”
确实没有保证,但当前的流实现使用了common Fork/Join pool can be configured to a desired number of threads。将指定数量的线程专用于任务正是您要求的策略。
“事实上,可能会有少量元素被执行繁重的任务。”
我认为F / J框架的工作原理并不矛盾。它将尝试拆分,直到达到所需的并行度。如果这意味着每个线程只对单个项目起作用,那么它就可以了。
此时,值得注意的是,与内核数量相匹配的默认并行性对于任何不涉及阻塞的计算任务都是足够的,无论处理单个项目需要多少时间。只要每个线程都有自己的工作负载,就不会有超出实际硬件执行单元数量的加速。
换句话说,F / J框架实现了您想要自己实现的策略(或者优于您实现的策略),这使我们回到第一点,过早优化。