Skip to content

Commit ddcbd7b

Browse files
authored
4.x: Streamable optimizations 2026.07.21 (#8254)
* 4.x: Streamable optimizations & fusion expansions * blockingX to accept StreamerCancellation for external management * Fix style and validator reported errors
1 parent db01b1f commit ddcbd7b

20 files changed

Lines changed: 805 additions & 44 deletions

src/jmh/java/io/reactivex/rxjava4/streamable/StreamableSkipPerf.java

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313

1414
package io.reactivex.rxjava4.streamable;
1515

16+
import java.util.List;
1617
import java.util.concurrent.TimeUnit;
18+
import java.util.stream.Collectors;
1719

1820
import org.openjdk.jmh.annotations.*;
1921

20-
import io.reactivex.rxjava4.core.*;
22+
import io.reactivex.rxjava4.core.Streamable;
2123

2224
///
2325
/// The map is one of the most used operator in the ecosystem so it must be fast and
@@ -66,6 +68,48 @@
6668
/// StreamableSkipPerf.benchmark 100000 thrpt 5 2168,925 ┬▒ 32,591 ops/s
6769
/// StreamableSkipPerf.benchmark 1000000 thrpt 5 214,055 ┬▒ 5,629 ops/s
6870
/// ```
71+
///
72+
/// # 3. sync bias via Claude Fable atomics
73+
///
74+
/// Small regression on 1, but +200% on million vs 2., almost +410% vs baseline
75+
///
76+
/// ```
77+
/// Benchmark (times) Mode Cnt Score Error Units
78+
/// StreamableSkipPerf.benchmark 1 thrpt 5 1392748873,097 ┬▒ 240564597,648 ops/s
79+
/// StreamableSkipPerf.benchmark 10 thrpt 5 50478482,677 ┬▒ 2503440,823 ops/s
80+
/// StreamableSkipPerf.benchmark 100 thrpt 5 8260512,961 ┬▒ 832256,445 ops/s
81+
/// StreamableSkipPerf.benchmark 1000 thrpt 5 617903,033 ┬▒ 127135,231 ops/s
82+
/// StreamableSkipPerf.benchmark 10000 thrpt 5 65116,345 ┬▒ 8425,364 ops/s
83+
/// StreamableSkipPerf.benchmark 100000 thrpt 5 6298,539 ┬▒ 739,686 ops/s
84+
/// StreamableSkipPerf.benchmark 1000000 thrpt 5 634,945 ┬▒ 99,134 ops/s
85+
/// ```
86+
///
87+
/// # 4. indexable/enumerable/deferredenumerable
88+
///
89+
/// ```
90+
/// Benchmark (times) Mode Cnt Score Error Units
91+
/// StreamableSkipPerf.benchmark 1 thrpt 5 1371065918,333 ┬▒ 114154178,555 ops/s
92+
/// StreamableSkipPerf.benchmark 10 thrpt 5 48387844,296 ┬▒ 1998160,368 ops/s
93+
/// StreamableSkipPerf.benchmark 100 thrpt 5 8344391,259 ┬▒ 251723,213 ops/s
94+
/// StreamableSkipPerf.benchmark 1000 thrpt 5 700332,413 ┬▒ 28667,777 ops/s
95+
/// StreamableSkipPerf.benchmark 10000 thrpt 5 68645,229 ┬▒ 3123,103 ops/s
96+
/// StreamableSkipPerf.benchmark 100000 thrpt 5 6047,152 ┬▒ 725,594 ops/s
97+
/// StreamableSkipPerf.benchmark 1000000 thrpt 5 649,134 ┬▒ 36,349 ops/s
98+
/// StreamableSkipPerf.enumerable 1 thrpt 5 71117553,552 ┬▒ 6738602,495 ops/s
99+
/// StreamableSkipPerf.enumerable 10 thrpt 5 43426136,653 ┬▒ 2744303,836 ops/s
100+
/// StreamableSkipPerf.enumerable 100 thrpt 5 2819347,969 ┬▒ 20437,839 ops/s
101+
/// StreamableSkipPerf.enumerable 1000 thrpt 5 298000,822 ┬▒ 28754,714 ops/s
102+
/// StreamableSkipPerf.enumerable 10000 thrpt 5 28839,499 ┬▒ 3096,840 ops/s
103+
/// StreamableSkipPerf.enumerable 100000 thrpt 5 2657,147 ┬▒ 23,023 ops/s
104+
/// StreamableSkipPerf.enumerable 1000000 thrpt 5 187,770 ┬▒ 42,082 ops/s
105+
/// StreamableSkipPerf.indexed 1 thrpt 5 75403131,814 ┬▒ 4676965,390 ops/s
106+
/// StreamableSkipPerf.indexed 10 thrpt 5 42730509,975 ┬▒ 6004178,786 ops/s
107+
/// StreamableSkipPerf.indexed 100 thrpt 5 3561284,547 ┬▒ 381609,301 ops/s
108+
/// StreamableSkipPerf.indexed 1000 thrpt 5 346781,462 ┬▒ 28909,008 ops/s
109+
/// StreamableSkipPerf.indexed 10000 thrpt 5 54254,177 ┬▒ 7440,753 ops/s
110+
/// StreamableSkipPerf.indexed 100000 thrpt 5 5153,256 ┬▒ 310,895 ops/s
111+
/// StreamableSkipPerf.indexed 1000000 thrpt 5 259,912 ┬▒ 76,746 ops/s
112+
/// ```
69113
@BenchmarkMode(Mode.Throughput)
70114
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
71115
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@@ -77,14 +121,28 @@ public class StreamableSkipPerf {
77121
public int times;
78122

79123
Streamable<Integer> result;
124+
Streamable<List<Integer>> resultIndexed;
125+
Streamable<List<Integer>> resultEnumerable;
80126

81127
@Setup
82128
public void setup() {
83129
result = Streamable.range(1, times).skip(times / 2);
130+
resultIndexed = Streamable.range(1, times).skip(times / 2).collect(Collectors.toList());
131+
resultEnumerable = Streamable.range(1, times).filter(_ -> true).skip(times / 2).collect(Collectors.toList());
84132
}
85133

86134
@Benchmark
87135
public Object benchmark() {
88136
return result.blockingLast();
89137
}
138+
139+
@Benchmark
140+
public Object indexed() {
141+
return resultIndexed.blockingLast();
142+
}
143+
144+
@Benchmark
145+
public Object enumerable() {
146+
return resultEnumerable.blockingLast();
147+
}
90148
}

src/main/java/io/reactivex/rxjava4/core/Streamable.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,21 @@ static <T> Streamable<List<T>> zip(Iterable<? extends Streamable<? extends T>> s
623623
default T blockingFirst() {
624624
return StreamableBlocking.blockingFirst(this);
625625
}
626+
/**
627+
* Blocks the current thread until this {@code Streamable} produces one item, which is then returned.
628+
* @param cancellation the external cancellation resource to pass into the chain
629+
* @return the first item of this {@code Streamable}
630+
* @throws NoSuchElementException if the this {@code Streamable} is empty
631+
* @throws CancellationException if this {@code Streamable} failed with a checked exception
632+
* @throws RuntimeException if this {@code Streamable} failed with an unchecked exception
633+
* @throws NullPointerException if {@code cancellation} is {@code null}
634+
*/
635+
@CheckReturnValue
636+
@NonNull
637+
default T blockingFirst(StreamerCancellation cancellation) {
638+
Objects.requireNonNull(cancellation, "cancellation is null");
639+
return StreamableBlocking.blockingFirst(this, cancellation);
640+
}
626641

627642
/**
628643
* Blocks the current thread until this {@code Streamable} produces all of its items
@@ -638,6 +653,23 @@ default T blockingLast() {
638653
return StreamableBlocking.blockingLast(this);
639654
}
640655

656+
/**
657+
* Blocks the current thread until this {@code Streamable} produces all of its items
658+
* and the very last is then returned.
659+
* @param cancellation the external cancellation resource to pass into the chain
660+
* @return the very last item of this {@code Streamable}
661+
* @throws NoSuchElementException if the this {@code Streamable} is empty
662+
* @throws CancellationException if this {@code Streamable} failed with a checked exception
663+
* @throws RuntimeException if this {@code Streamable} failed with an unchecked exception
664+
* @throws NullPointerException if {@code cancellation} is {@code null}
665+
*/
666+
@CheckReturnValue
667+
@NonNull
668+
default T blockingLast(StreamerCancellation cancellation) {
669+
Objects.requireNonNull(cancellation, "cancellation is null");
670+
return StreamableBlocking.blockingLast(this, cancellation);
671+
}
672+
641673
/**
642674
* Collects all upstream values via the use of a {@link Collector} configuration
643675
* and emits its resulting value as a single item of the returned {@code Streamable}.

src/main/java/io/reactivex/rxjava4/disposables/DisposableStreamerCancellation.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
package io.reactivex.rxjava4.disposables;
1515

16+
import io.reactivex.rxjava4.internal.disposables.NeverDisposableStreamerCancellation;
17+
1618
/**
1719
* Represents the full, disposable cancellation interface for {@code Streamer}
1820
* operations.
@@ -23,4 +25,13 @@
2325
*/
2426
public interface DisposableStreamerCancellation extends StreamerCancellation, Disposable {
2527

28+
/**
29+
* Returns a constant instance which does nothing, cannot be disposed and
30+
* accepts any incoming Disposable without registering it or handling it in any form,
31+
* because this {@code never} instance cannot be disposed to begin with.
32+
* @return the shared constant no-op instance
33+
*/
34+
static DisposableStreamerCancellation never() {
35+
return NeverDisposableStreamerCancellation.INSTANCE;
36+
}
2637
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2016-present, RxJava Contributors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
5+
* compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is
10+
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
11+
* the License for the specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package io.reactivex.rxjava4.internal.disposables;
15+
16+
import io.reactivex.rxjava4.annotations.NonNull;
17+
import io.reactivex.rxjava4.disposables.*;
18+
19+
/// A [DisposableStreamerCancellation] handler that does nothing.
20+
public enum NeverDisposableStreamerCancellation implements DisposableStreamerCancellation {
21+
INSTANCE
22+
;
23+
24+
@Override
25+
public boolean isDisposed() {
26+
return false; // always because it has no state
27+
}
28+
29+
@Override
30+
public boolean add(@NonNull Disposable d) {
31+
return true; // always succeeds to avoid infinite retry loops
32+
}
33+
34+
@Override
35+
public boolean remove(@NonNull Disposable d) {
36+
return true; // always succeeds
37+
}
38+
39+
@Override
40+
public boolean delete(@NonNull Disposable d) {
41+
return true; // always succeeds
42+
}
43+
44+
@Override
45+
public @NonNull DisposableStreamerCancellation derive() {
46+
return this;
47+
}
48+
49+
@Override
50+
public void dispose() {
51+
// deliberately no-op
52+
}
53+
54+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2016-present, RxJava Contributors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
5+
* compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is
10+
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
11+
* the License for the specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package io.reactivex.rxjava4.internal.operators.streamable;
15+
16+
import io.reactivex.rxjava4.core.*;
17+
import io.reactivex.rxjava4.operators.EnumerableSource;
18+
19+
/// Marker interface to indicate a [Streamable] source will produce
20+
/// an [EnumerableSource]-enabled [Streamer] and thus enables
21+
/// optimizations and operator fusion during assembly time.
22+
/// @param <T> the element type of the `Streamable`
23+
/// @since 4.0.0
24+
public interface IsEnumerableStreamable<T> extends Streamable<T> {
25+
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2016-present, RxJava Contributors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
5+
* compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is
10+
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
11+
* the License for the specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package io.reactivex.rxjava4.internal.operators.streamable;
15+
16+
import io.reactivex.rxjava4.core.*;
17+
import io.reactivex.rxjava4.operators.IndexableSource;
18+
19+
/// Marker interface to indicate a [Streamable] source will produce
20+
/// an [IndexableSource]-enabled [Streamer] and thus enables
21+
/// optimizations and operator fusion during assembly time.
22+
/// @param <T> the element type of the `Streamable`
23+
/// @since 4.0.0
24+
public interface IsIndexableStreamable<T> extends Streamable<T> {
25+
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2016-present, RxJava Contributors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
5+
* compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is
10+
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
11+
* the License for the specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package io.reactivex.rxjava4.internal.operators.streamable;
15+
16+
import io.reactivex.rxjava4.core.*;
17+
import io.reactivex.rxjava4.operators.*;
18+
19+
/// Marker interface to indicate a [Streamable] source will produce
20+
/// both an [IndexableSource] and an [EnumerableSource] capable
21+
/// [Streamer].
22+
/// @param <T> the element type of the `Streamable`
23+
/// @since 4.0.0
24+
public interface IsSynchronousStreamable<T> extends IsIndexableStreamable<T>, IsEnumerableStreamable<T> {
25+
26+
}

src/main/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableBlocking.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import io.reactivex.rxjava4.annotations.*;
2020
import io.reactivex.rxjava4.core.Streamable;
21-
import io.reactivex.rxjava4.disposables.CompositeDisposable;
21+
import io.reactivex.rxjava4.disposables.*;
2222
import io.reactivex.rxjava4.exceptions.Exceptions;
2323
import io.reactivex.rxjava4.internal.util.ExceptionHelper;
2424

@@ -36,7 +36,23 @@ public record StreamableBlocking() {
3636
@CheckReturnValue
3737
@NonNull
3838
public static <T> T blockingFirst(Streamable<T> source) {
39-
var streamer = source.stream(new CompositeDisposable());
39+
return blockingFirst(source, new CompositeDisposable());
40+
}
41+
42+
/**
43+
* Consumes the first item and finishes the {@link Streamable},
44+
* throwing {@link NoSuchElementException} if the source is empty.
45+
* @param <T> the element type
46+
* @param source the source {@code Streamable}
47+
* @param cancellation the external cancellation manager
48+
* @return the first item
49+
* @throws RuntimeException if the source signals an unchecked exception
50+
* @throws CompletionException if the source signals a checked exception
51+
*/
52+
@CheckReturnValue
53+
@NonNull
54+
public static <T> T blockingFirst(Streamable<T> source, StreamerCancellation cancellation) {
55+
var streamer = source.stream(cancellation);
4056
Throwable nextException = null;
4157
Throwable finishException = null;
4258
T result = null;
@@ -74,7 +90,21 @@ public static <T> T blockingFirst(Streamable<T> source) {
7490
* @throws CompletionException if the source signals a checked exception
7591
*/
7692
public static <T> T blockingLast(Streamable<T> source) {
77-
var streamer = source.stream(new CompositeDisposable());
93+
return blockingLast(source, new CompositeDisposable());
94+
}
95+
96+
/**
97+
* Consumes all upstream items and returns the very last or throws
98+
* a {@link NoSuchElementException}.
99+
* @param <T> the element type
100+
* @param source the source sequence
101+
* @param cancellation the external cancellation manager
102+
* @return the very last value
103+
* @throws RuntimeException if the source signals an unchecked exception
104+
* @throws CompletionException if the source signals a checked exception
105+
*/
106+
public static <T> T blockingLast(Streamable<T> source, StreamerCancellation cancellation) {
107+
var streamer = source.stream(cancellation);
78108
Throwable nextException = null;
79109
Throwable finishException = null;
80110
T result = null;

src/main/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableEmpty.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import io.reactivex.rxjava4.disposables.StreamerCancellation;
2222
import io.reactivex.rxjava4.operators.*;
2323

24-
public enum StreamableEmpty implements Streamable<Object> {
24+
public enum StreamableEmpty implements IsSynchronousStreamable<Object> {
2525

2626
INSTANCE;
2727

src/main/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableJust.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import io.reactivex.rxjava4.disposables.StreamerCancellation;
2121
import io.reactivex.rxjava4.operators.*;
2222

23-
public record StreamableJust<T>(@NonNull T item) implements Streamable<T> {
23+
public record StreamableJust<T>(@NonNull T item) implements IsSynchronousStreamable<T> {
2424

2525
@Override
2626
public @NonNull Streamer<@NonNull T> stream(@NonNull StreamerCancellation cancellation) {

0 commit comments

Comments
 (0)