/
wstartsev
/
jdk
Обзор
Документация
Войти
/
wstartsev
/
jdk
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/java.base/share/classes/java/util/stream/GathererOp.java
740 строк
28 KB
Sergey Bylokhov
8374327: Update copyright year to 2025 for files in java.base added/updated by commits in 2025
31 дек 2025, 12:13
31 дек 2025, 12:13
36d2c27
Код
Авторство
О чём код?
/* * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.util.stream; import jdk.internal.invoke.MhUtil; import jdk.internal.vm.annotation.ForceInline; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.Spliterator; import java.util.concurrent.CountedCompleter; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; import java.util.stream.Gatherer.Integrator; /** * Runtime machinery for evaluating Gatherers under different modes. * The performance-critical code below contains some more complicated encodings: * therefore, make sure to run benchmarks to verify changes to prevent regressions. * * @since 24 */ final class GathererOp<T, A, R> extends ReferencePipeline<T, R> { @SuppressWarnings("unchecked") static <P_IN, P_OUT extends T, T, A, R> Stream<R> of( ReferencePipeline<P_IN, P_OUT> upstream, Gatherer<T, A, R> gatherer) { // When attaching a gather-operation onto another gather-operation, // we can fuse them into one if (upstream.getClass() == GathererOp.class) { return new GathererOp<>( ((GathererOp<P_IN, Object, P_OUT>) upstream).gatherer.andThen(gatherer), (GathererOp<?, ?, P_IN>) upstream); } else { return new GathererOp<>( (ReferencePipeline<?, T>) upstream, gatherer); } } /* * GathererOp.NodeBuilder is a lazy accumulator of elements with O(1) * `append`, and O(8) `join` (concat). * * First `append` inflates a growable Builder, the O(8) for `join` is * because we prefer to delegate to `append` for small concatenations to * avoid excessive indirections (unbalanced Concat-trees) when joining many * NodeBuilders together. */ static final class NodeBuilder<X> implements Consumer<X> { private static final int LINEAR_APPEND_MAX = 8; // TODO revisit static final class Builder<X> extends SpinedBuffer<X> implements Node<X> { Builder() { } } NodeBuilder() { } private Builder<X> rightMost; private Node<X> leftMost; private boolean isEmpty() { return rightMost == null && leftMost == null; } @Override public void accept(X x) { final var b = rightMost; (b == null ? (rightMost = new NodeBuilder.Builder<>()) : b).accept(x); } public NodeBuilder<X> join(NodeBuilder<X> that) { if (isEmpty()) return that; if (!that.isEmpty()) { final var tb = that.build(); if (rightMost != null && tb instanceof NodeBuilder.Builder<X> && tb.count() < LINEAR_APPEND_MAX) tb.forEach(this); // Avoid conc for small nodes else leftMost = Nodes.conc(StreamShape.REFERENCE, this.build(), tb); } return this; } public Node<X> build() { if (isEmpty()) return Nodes.emptyNode(StreamShape.REFERENCE); final var rm = rightMost; if (rm != null) { rightMost = null; // Make sure builder isn't reused final var lm = leftMost; leftMost = (lm == null) ? rm : Nodes.conc(StreamShape.REFERENCE, lm, rm); } return leftMost; } } static final class GatherSink<T, A, R> implements Sink<T>, Gatherer.Downstream<R> { private final Sink<R> sink; private final Gatherer<T, A, R> gatherer; private final Integrator<A, T, R> integrator; // Optimization: reuse private A state; private boolean proceed = true; private boolean downstreamProceed = true; GatherSink(Gatherer<T, A, R> gatherer, Sink<R> sink) { this.gatherer = gatherer; this.sink = sink; this.integrator = gatherer.integrator(); } // java.util.stream.Sink contract below: @Override public void begin(long size) { final var initializer = gatherer.initializer(); if (initializer != Gatherer.defaultInitializer()) // Optimization state = initializer.get(); sink.begin(-1); // GathererOp does not know the size of the output } @Override public void accept(T t) { /* Benchmarks have indicated that doing an unconditional write to * `proceed` is more efficient than branching. * We use `&=` here to prevent flips from `false` -> `true`. * * As of writing this, taking `greedy` or `stateless` into * consideration at this point doesn't yield any performance gains. */ proceed &= integrator.integrate(state, t, this); } @Override public boolean cancellationRequested() { return cancellationRequested(proceed && downstreamProceed); } private boolean cancellationRequested(boolean knownProceed) { // Highly performance sensitive return !(knownProceed && (!sink.cancellationRequested() || (downstreamProceed = false))); } @Override public void end() { final var finisher = gatherer.finisher(); if (finisher != Gatherer.<A, R>defaultFinisher()) // Optimization finisher.accept(state, this); sink.end(); state = null; // GC assistance } // Gatherer.Sink contract below: @Override public boolean isRejecting() { return !downstreamProceed; } @Override public boolean push(R r) { var p = downstreamProceed; if (p) sink.accept(r); return !cancellationRequested(p); } } private static int opFlagsFor(Integrator<?, ?, ?> integrator) { return integrator instanceof Integrator.Greedy<?, ?, ?> ? GREEDY_FLAGS : SHORT_CIRCUIT_FLAGS; } private static final int DEFAULT_FLAGS = StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT | StreamOpFlag.NOT_SIZED; private static final int SHORT_CIRCUIT_FLAGS = DEFAULT_FLAGS | StreamOpFlag.IS_SHORT_CIRCUIT; private static final int GREEDY_FLAGS = DEFAULT_FLAGS; final Gatherer<T, A, R> gatherer; /* * This constructor is used for initial .gather() invocations */ private GathererOp(ReferencePipeline<?, T> upstream, Gatherer<T, A, R> gatherer) { /* TODO this is a prime spot for pre-super calls to make sure that * we only need to call `integrator()` once. */ super(upstream, opFlagsFor(gatherer.integrator())); this.gatherer = gatherer; } /* * This constructor is used when fusing subsequent .gather() invocations */ @SuppressWarnings("unchecked") private GathererOp(Gatherer<T, A, R> gatherer, GathererOp<?, ?, T> upstream) { super((AbstractPipeline<?, T, ?>) upstream.upstream(), upstream, opFlagsFor(gatherer.integrator())); this.gatherer = gatherer; } /* This allows internal access to the previous stage, * to be able to fuse `gather` followed by `collect`. */ @SuppressWarnings("unchecked") private AbstractPipeline<?, T, ?> upstream() { return (AbstractPipeline<?, T, ?>) super.previousStage; } @Override boolean opIsStateful() { // TODO /* Currently GathererOp is always stateful, but what could be tried is: * return gatherer.initializer() != Gatherer.defaultInitializer() * || gatherer.combiner() == Gatherer.defaultCombiner() * || gatherer.finisher() != Gatherer.defaultFinisher(); */ return true; } @Override Sink<T> opWrapSink(int flags, Sink<R> downstream) { return new GatherSink<>(gatherer, downstream); } /* * This is used when evaluating .gather() operations interspersed with * other Stream operations (in parallel) */ @Override <I> Node<R> opEvaluateParallel(PipelineHelper<R> unused1, Spliterator<I> spliterator, IntFunction<R[]> unused2) { return this.<NodeBuilder<R>, Node<R>>evaluate( upstream().wrapSpliterator(spliterator), true, gatherer, NodeBuilder::new, NodeBuilder::accept, NodeBuilder::join, NodeBuilder::build ); } @Override <P_IN> Spliterator<R> opEvaluateParallelLazy(PipelineHelper<R> helper, Spliterator<P_IN> spliterator) { /* * There's a very small subset of possible Gatherers which would be * expressible as Spliterators directly, * - the Gatherer's initializer is Gatherer.defaultInitializer(), * - the Gatherer's combiner is NOT Gatherer.defaultCombiner() * - the Gatherer's finisher is Gatherer.defaultFinisher() */ return opEvaluateParallel(null, spliterator, null).spliterator(); } /* gather-operations immediately followed by (terminal) collect-operations * are fused together to avoid having to first run the gathering to * completion and only after that be able to run the collection on top of * the output. This is highly beneficial in the parallel case as stateful * operations cannot be pipelined in the ReferencePipeline implementation. * Overriding collect-operations overcomes this limitation. */ @Override public <CR, CA> CR collect(Collector<? super R, CA, CR> c) { linkOrConsume(); // Important for structural integrity final var parallel = isParallel(); final var u = upstream(); return evaluate( u.wrapSpliterator(u.sourceSpliterator(0)), parallel, gatherer, c.supplier(), c.accumulator(), parallel ? c.combiner() : null, c.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH) ? null : c.finisher() ); } @Override public <RR> RR collect(Supplier<RR> supplier, BiConsumer<RR, ? super R> accumulator, BiConsumer<RR, RR> combiner) { linkOrConsume(); // Important for structural integrity final var parallel = isParallel(); final var u = upstream(); return evaluate( u.wrapSpliterator(u.sourceSpliterator(0)), parallel, gatherer, supplier, accumulator, parallel ? (l, r) -> { combiner.accept(l, r); return l; } : null, null ); } /* * evaluate(...) is the primary execution mechanism besides opWrapSink() * and implements both sequential, hybrid parallel-sequential, and * parallel evaluation */ private <CA, CR> CR evaluate(final Spliterator<T> spliterator, final boolean parallel, final Gatherer<T, A, R> gatherer, final Supplier<CA> collectorSupplier, final BiConsumer<CA, ? super R> collectorAccumulator, final BinaryOperator<CA> collectorCombiner, final Function<CA, CR> collectorFinisher) { // There are two main sections here: sequential and parallel final var initializer = gatherer.initializer(); final var integrator = gatherer.integrator(); // Optimization final boolean greedy = integrator instanceof Integrator.Greedy<A, T, R>; // Sequential evaluation section starts here. // Sequential is the fusion of a Gatherer and a Collector which can // be evaluated sequentially. final class Sequential implements Consumer<T>, Gatherer.Downstream<R> { A state; CA collectorState; boolean proceed; Sequential() { if (initializer != Gatherer.defaultInitializer()) state = initializer.get(); collectorState = collectorSupplier.get(); proceed = true; } @ForceInline Sequential evaluateUsing(Spliterator<T> spliterator) { if (greedy) spliterator.forEachRemaining(this); else do { } while (proceed && spliterator.tryAdvance(this)); return this; } /* * No need to override isKnownDone() as the default is `false` * and collectors can never short-circuit. */ @Override public boolean push(R r) { collectorAccumulator.accept(collectorState, r); return true; } @Override public void accept(T t) { /* * Benchmarking has shown that, in this case, conditional * writing of `proceed` is desirable and if that was not the * case, then the following line would've been clearer: * * proceed &= integrator.integrate(state, t, this); */ var ignore = integrator.integrate(state, t, this) || (!greedy && (proceed = false)); } @SuppressWarnings("unchecked") public CR get() { final var finisher = gatherer.finisher(); if (finisher != Gatherer.<A, R>defaultFinisher()) finisher.accept(state, this); // IF collectorFinisher == null -> IDENTITY_FINISH return (collectorFinisher == null) ? (CR) collectorState : collectorFinisher.apply(collectorState); } } /* * It could be considered to also go to sequential mode if the * operation is non-greedy AND the combiner is Gatherer.defaultCombiner() * as those operations will not benefit from upstream parallel * preprocessing which is the main advantage of the Hybrid evaluation * strategy. */ if (!parallel) return new Sequential().evaluateUsing(spliterator).get(); // Parallel section starts here: final var combiner = gatherer.combiner(); /* * The following implementation of hybrid parallel-sequential * Gatherer processing borrows heavily from ForeachOrderedTask, * and adds handling of short-circuiting. */ @SuppressWarnings("serial") final class Hybrid extends CountedCompleter<Sequential> { private final long targetSize; private final Hybrid leftPredecessor; private final AtomicBoolean cancelled; private final Sequential localResult; private Spliterator<T> spliterator; private Hybrid next; private static final VarHandle NEXT = MhUtil.findVarHandle( MethodHandles.lookup(), "next", Hybrid.class); protected Hybrid(Spliterator<T> spliterator) { super(null); this.spliterator = spliterator; this.targetSize = AbstractTask.suggestTargetSize(spliterator.estimateSize()); this.localResult = new Sequential(); this.cancelled = greedy ? null : new AtomicBoolean(false); this.leftPredecessor = null; } Hybrid(Hybrid parent, Spliterator<T> spliterator, Hybrid leftPredecessor) { super(parent); this.spliterator = spliterator; this.targetSize = parent.targetSize; this.localResult = parent.localResult; this.cancelled = parent.cancelled; this.leftPredecessor = leftPredecessor; } @Override public Sequential getRawResult() { return localResult; } @Override public void setRawResult(Sequential result) { if (result != null) throw new IllegalStateException(); } @Override public void compute() { var task = this; Spliterator<T> rightSplit = task.spliterator, leftSplit; long sizeThreshold = task.targetSize; boolean forkRight = false; while ((greedy || !cancelled.get()) && rightSplit.estimateSize() > sizeThreshold && (leftSplit = rightSplit.trySplit()) != null) { var leftChild = new Hybrid(task, leftSplit, task.leftPredecessor); var rightChild = new Hybrid(task, rightSplit, leftChild); /* leftChild and rightChild were just created and not * fork():ed yet so no need for a volatile write */ leftChild.next = rightChild; // Fork the parent task // Completion of the left and right children "happens-before" // completion of the parent task.addToPendingCount(1); // Completion of the left child "happens-before" completion of // the right child rightChild.addToPendingCount(1); // If task is not on the left spine if (task.leftPredecessor != null) { /* * Completion of left-predecessor, or left subtree, * "happens-before" completion of left-most leaf node of * right subtree. * The left child's pending count needs to be updated before * it is associated in the completion map, otherwise the * left child can complete prematurely and violate the * "happens-before" constraint. */ leftChild.addToPendingCount(1); // Update association of left-predecessor to left-most // leaf node of right subtree if (NEXT.compareAndSet(task.leftPredecessor, task, leftChild)) { // If replaced, adjust the pending count of the parent // to complete when its children complete task.addToPendingCount(-1); } else { // Left-predecessor has already completed, parent's // pending count is adjusted by left-predecessor; // left child is ready to complete leftChild.addToPendingCount(-1); } } if (forkRight) { rightSplit = leftSplit; task = leftChild; rightChild.fork(); } else { task = rightChild; leftChild.fork(); } forkRight = !forkRight; } /* * Task's pending count is either 0 or 1. If 1 then the completion * map will contain a value that is task, and two calls to * tryComplete are required for completion, one below and one * triggered by the completion of task's left-predecessor in * onCompletion. Therefore there is no data race within the if * block. * * IMPORTANT: Currently we only perform the processing of this * upstream data if we know the operation is greedy -- as we cannot * safely speculate on the cost/benefit ratio of parallelizing * the pre-processing of upstream data under short-circuiting. */ if (greedy && task.getPendingCount() > 0) { // Upstream elements are buffered NodeBuilder<T> nb = new NodeBuilder<>(); rightSplit.forEachRemaining(nb); // Run the upstream task.spliterator = nb.build().spliterator(); } task.tryComplete(); } @Override public void onCompletion(CountedCompleter<?> caller) { var s = spliterator; spliterator = null; // GC assistance /* Performance sensitive since each leaf-task could have a * spliterator of size 1 which means that all else is overhead * which needs minimization. */ if (s != null && (greedy || !cancelled.get()) && !localResult.evaluateUsing(s).proceed && !greedy) cancelled.set(true); // The completion of this task *and* the dumping of elements // "happens-before" completion of the associated left-most leaf task // of right subtree (if any, which can be this task's right sibling) @SuppressWarnings("unchecked") var leftDescendant = (Hybrid) NEXT.getAndSet(this, null); if (leftDescendant != null) { leftDescendant.tryComplete(); } } } /* * The following implementation of parallel Gatherer processing * borrows heavily from AbstractShortCircuitTask */ @SuppressWarnings("serial") final class Parallel extends CountedCompleter<Sequential> { private Spliterator<T> spliterator; private Parallel leftChild; // Only non-null if rightChild is private Parallel rightChild; // Only non-null if leftChild is private Sequential localResult; private volatile boolean canceled; private long targetSize; // lazily initialized private Parallel(Parallel parent, Spliterator<T> spliterator) { super(parent); this.targetSize = parent.targetSize; this.spliterator = spliterator; } Parallel(Spliterator<T> spliterator) { super(null); this.targetSize = 0L; this.spliterator = spliterator; } private long getTargetSize(long sizeEstimate) { long s; return ((s = targetSize) != 0 ? s : (targetSize = AbstractTask.suggestTargetSize(sizeEstimate))); } @Override public Sequential getRawResult() { return localResult; } @Override public void setRawResult(Sequential result) { if (result != null) throw new IllegalStateException(); } private void doProcess() { if (!(localResult = new Sequential()).evaluateUsing(spliterator).proceed && !greedy) cancelLaterTasks(); } @Override public void compute() { Spliterator<T> rs = spliterator, ls; long sizeEstimate = rs.estimateSize(); final long sizeThreshold = getTargetSize(sizeEstimate); Parallel task = this; boolean forkRight = false; boolean proceed; while ((proceed = (greedy || !task.isRequestedToCancel())) && sizeEstimate > sizeThreshold && (ls = rs.trySplit()) != null) { final var leftChild = task.leftChild = new Parallel(task, ls); final var rightChild = task.rightChild = new Parallel(task, rs); task.setPendingCount(1); if (forkRight) { rs = ls; task = leftChild; rightChild.fork(); } else { task = rightChild; leftChild.fork(); } forkRight = !forkRight; sizeEstimate = rs.estimateSize(); } if (proceed) task.doProcess(); task.tryComplete(); } Sequential merge(Sequential l, Sequential r) { /* * Only join the right if the left side didn't short-circuit, * or when greedy */ if (greedy || (l != null && r != null && l.proceed)) { l.state = combiner.apply(l.state, r.state); l.collectorState = collectorCombiner.apply(l.collectorState, r.collectorState); l.proceed = r.proceed; return l; } return (l != null) ? l : r; } @Override public void onCompletion(CountedCompleter<?> caller) { spliterator = null; // GC assistance if (leftChild != null) { /* Results can only be null in the case where there's * short-circuiting or when Gatherers are stateful but * uses `null` as their state value. */ localResult = merge(leftChild.localResult, rightChild.localResult); leftChild = rightChild = null; // GC assistance } } @SuppressWarnings("unchecked") private Parallel getParent() { return (Parallel) getCompleter(); } private boolean isRequestedToCancel() { boolean cancel = canceled; if (!cancel) { for (Parallel parent = getParent(); !cancel && parent != null; parent = parent.getParent()) cancel = parent.canceled; } return cancel; } private void cancelLaterTasks() { // Go up the tree, cancel right siblings of this node and all parents for (Parallel parent = getParent(), node = this; parent != null; node = parent, parent = parent.getParent()) { // If node is a left child of parent, then has a right sibling if (parent.leftChild == node) parent.rightChild.canceled = true; } } } if (combiner != Gatherer.defaultCombiner()) return new Parallel(spliterator).invoke().get(); else return new Hybrid(spliterator).invoke().get(); } }