{@code class ForEachAs a further improvement, notice that the left task need not even exist. Instead of creating a new one, we can iterate using the original task, and add a pending count for each fork:...}public void compute() // version 2 if (hi - lo >= 2) { int mid = (lo + hi) >>> 1; setPendingCount(1); // only one pending new ForEach(this, array, op, mid, hi).fork(); // right child new ForEach(this, array, op, lo, mid).compute(); // direct invoke } else { if (hi > lo) op.apply(array[lo]); tryComplete(); } } }
{@code class ForEachAdditional improvements of such classes might entail precomputing pending counts so that they can be established in constructors, specializing classes for leaf steps, subdividing by say, four, instead of two per iteration, and using an adaptive threshold instead of always subdividing down to single elements....}public void compute() // version 3 int l = lo, h = hi; while (h - l >= 2) { int mid = (l + h) >>> 1; addToPendingCount(1); new ForEach(this, array, op, mid, h).fork(); // right child h = mid; } if (h > l) op.apply(array[l]); tryComplete(); } }
Recording subtasks. CountedCompleter tasks that combine results of multiple subtasks usually need to access these results in method {@link #onCompletion}. As illustrated in the following class (that performs a simplified form of map-reduce where mappings and reductions are all of type {@code E}), one way to do this in divide and conquer designs is to have each subtask record its sibling, so that it can be accessed in method {@code onCompletion}. For clarity, this class uses explicit left and right subtasks, but variants of other streamlinings seen in the above example may also apply.
{@code}class MyMapperE apply(E v) { ... } } class MyReducer { E apply(E x, E y) { ... } } class MapReducer extends CountedCompleter { final E[] array; final MyMapper mapper; final MyReducer reducer; final int lo, hi; MapReducer sibling; E result; MapReducer(CountedCompleter p, E[] array, MyMapper mapper, MyReducer reducer, int lo, int hi) { super(p); this.array = array; this.mapper = mapper; this.reducer = reducer; this.lo = lo; this.hi = hi; } public void compute() { if (hi - lo >= 2) { int mid = (lo + hi) >>> 1; MapReducer left = new MapReducer(this, array, mapper, reducer, lo, mid); MapReducer right = new MapReducer(this, array, mapper, reducer, mid, hi); left.sibling = right; right.sibling = left; setPendingCount(1); // only right is pending right.fork(); left.compute(); // directly execute left } else { if (hi > lo) result = mapper.apply(array[lo]); tryComplete(); } } public void onCompletion(CountedCompleter caller) { if (caller != this) { MapReducer child = (MapReducer )caller; MapReducer sib = child.sibling; if (sib == null || sib.result == null) result = child.result; else result = reducer.apply(child.result, sib.result); } } public static E mapReduce(ForkJoinPool pool, E[] array, MyMapper mapper, MyReducer reducer) { MapReducer mr = new MapReducer (null, array, mapper, reducer, 0, array.length); pool.invoke(mr); return mr.result; } } }
Triggers. Some CountedCompleters are themselves never forked, but instead serve as bits of plumbing in other designs; including those in which the completion of one of more async tasks triggers another async task. For example:
{@code}class HeaderBuilder extends CountedCompleter ... } class BodyBuilder extends CountedCompleter { ... } class PacketSender extends CountedCompleter { PacketSender(...) { super(null, 1); ... } // trigger on second completion public void compute() { } // never called public void onCompletion(CountedCompleter caller) { sendPacket(); } } // sample use: PacketSender p = new PacketSender(); new HeaderBuilder(p, ...).fork(); new BodyBuilder(p, ...).fork(); }@since 1.8 @author Doug Lea
|
|