diff --git a/README.md b/README.md
index d4378d9..f7fdf79 100644
--- a/README.md
+++ b/README.md
@@ -618,73 +618,73 @@ dotnet run --project src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReactiveUI.
Short local jobs are useful for fast regression checks; rerun with a longer BenchmarkDotNet job before making release claims. Current local test coverage after this pass is 82.80% line coverage and 75.50% branch coverage from `coverage-local.cobertura.xml`; the 100% coverage target remains an active work item.
-`N/A` means the current benchmark harness does not contain a matching measured row for that library.
+The table below is generated from the joined BenchmarkDotNet CSV and uses `Mean / Allocated` for each cell.
| Scenario | ReactiveUI.Primitives | System.Reactive | R3 |
|---|---:|---:|---:|
-| Return subscribe | 0.2089 ns / 0 B | 45.7505 ns / 120 B | 28.1108 ns / 72 B |
-| Empty subscribe | 2.6805 ns / 40 B | 40.1536 ns / 96 B | 25.8399 ns / 48 B |
-| Range subscribe | 46.0466 ns / 96 B | 2,452.1549 ns / 2,472 B | 63.6342 ns / 72 B |
-| Repeat subscribe | 6.7455 ns / 0 B | 2,275.7650 ns / 2,408 B | 64.8692 ns / 72 B |
-| Throw subscribe | 54.9685 ns / 120 B | 106.8594 ns / 240 B | 85.8239 ns / 192 B |
-| FromEnumerable subscribe | 49.3764 ns / 40 B | 2,171.2470 ns / 2,504 B | 70.3934 ns / 80 B |
-| Completed task bridge | 8.7892 ns / 88 B | 769.1087 ns / 793 B | N/A |
-| Create subscribe | 43.7376 ns / 248 B | N/A | N/A |
-| CreateSafe subscribe | 44.1792 ns / 248 B | N/A | N/A |
-| Defer subscribe | 66.0839 ns / 240 B | N/A | N/A |
-| Start subscribe | 51.7062 ns / 376 B | N/A | N/A |
-| Unfold subscribe | 167.5562 ns / 736 B | N/A | N/A |
-| Use subscribe | 67.7116 ns / 432 B | N/A | N/A |
-| FromAsyncEnumerable subscribe | 1,921.1208 ns / 2,052 B | N/A | N/A |
-| Never subscribe/dispose | 0.2163 ns / 0 B | N/A | N/A |
-| Map + Keep over range | 130.0149 ns / 208 B | 2,461.1312 ns / 2,616 B | 256.9470 ns / 264 B |
-| Aggregate + Any + Count | 229.9964 ns / 992 B | 5,073.0502 ns / 5,896 B | 529.5892 ns / 1,280 B |
-| StartWith + Append + DefaultIfEmpty | 45.2433 ns / 184 B | 869.3671 ns / 1,257 B | 128.0685 ns / 280 B |
-| SelectMany over ranges | 939.2041 ns / 712 B | 3,357.9581 ns / 3,872 B | 965.1100 ns / 1,032 B |
-| Zip over ranges | 38.8399 ns / 232 B | 2,903.8925 ns / 2,976 B | 658.2362 ns / 648 B |
-| Concat ranges | 67.0788 ns / 256 B | N/A | N/A |
-| Merge ranges | 66.9464 ns / 256 B | N/A | N/A |
-| Race ranges | 37.7646 ns / 192 B | N/A | N/A |
-| Switch ranges | 797.3144 ns / 1,376 B | N/A | N/A |
-| CombineLatest ranges | 95.0119 ns / 504 B | N/A | N/A |
-| WithLatest ranges | 104.8306 ns / 504 B | N/A | N/A |
-| ForkJoin ranges | 67.7277 ns / 480 B | N/A | N/A |
-| Delay range | 3,132.9781 ns / 38,816 B | N/A | N/A |
-| DelayStart range | 874.3075 ns / 25,520 B | N/A | N/A |
-| Throttle burst | 2,504.3725 ns / 38,384 B | N/A | N/A |
-| Sample latest | 1,045.5780 ns / 26,072 B | N/A | N/A |
-| Timestamp range | 394.0507 ns / 312 B | N/A | N/A |
-| TimeInterval range | 478.5383 ns / 736 B | N/A | N/A |
-| Timeout never | 976.3098 ns / 25,816 B | N/A | N/A |
-| ObserveOn immediate | 21.8563 ns / 96 B | N/A | N/A |
-| Replay subscribe | 324.2733 ns / 320 B | 665.7033 ns / 696 B | N/A |
-| BehaviorSignal 32 values | 554.5077 ns / 176 B | 581.5846 ns / 200 B | 594.3121 ns / 184 B |
-| BehaviorSignal 1024 values | 15,698.1415 ns / 176 B | 15,826.6246 ns / 200 B | 15,702.7802 ns / 184 B |
-| Signal emit, 32 values | 65.8803 ns / 136 B | 90.0502 ns / 136 B | 116.1177 ns / 152 B |
-| Signal emit, 1024 values | 1,650.9938 ns / 136 B | 1,676.8777 ns / 136 B | 1,984.4349 ns / 152 B |
-| Signal subscribe/dispose, 8 observers | 240.6380 ns / 592 B | 284.9100 ns / 1,288 B | 450.5067 ns / 840 B |
-| Signal subscribe/dispose, 64 observers | 2,599.1562 ns / 3,800 B | 3,600.1331 ns / 38,472 B | 3,401.7292 ns / 6,216 B |
-| Publish live connect | 125.5809 ns / 384 B | N/A | N/A |
-| Share live subscribe | 225.6140 ns / 848 B | N/A | N/A |
-| Replay live late subscribe | 595.9243 ns / 568 B | N/A | N/A |
-| RefCount subscribe | 222.4420 ns / 848 B | N/A | N/A |
-| AutoConnect subscribe | 167.9847 ns / 728 B | N/A | N/A |
-| StateSignal updates | 552.4185 ns / 176 B | N/A | N/A |
-| ReadOnlyState projection | 123.5420 ns / 248 B | N/A | N/A |
-| TaskSignal subscribe | 2,384.2121 ns / 3,875 B | N/A | N/A |
-| Command execute | 114.0456 ns / 600 B | N/A | N/A |
-| Command result subscribe | 137.9245 ns / 672 B | N/A | N/A |
-| CollectList range | 115.9544 ns / 688 B | N/A | N/A |
-| CollectArray range | 83.1385 ns / 656 B | N/A | N/A |
-| CollectArrayAsync range | 33.8094 ns / 384 B | N/A | N/A |
-| FirstAsync range | 5.9320 ns / 56 B | N/A | N/A |
-| ToTask range | 13.9715 ns / 192 B | N/A | N/A |
-| Count(predicate) range | 55.0441 ns / 144 B | N/A | N/A |
-| All + Contains range | 204.1768 ns / 1,024 B | N/A | N/A |
-| Pocket dispose | 60.2873 ns / 408 B | 93.1830 ns / 512 B | N/A |
-| CurrentThread schedule | 12.4912 ns / 88 B | 14.7694 ns / 88 B | N/A |
-| Safe witness | 21.7079 ns / 168 B | N/A | N/A |
-| Completed Spark | 0.0006 ns / 0 B | N/A | N/A |
+| Return subscribe | 0.2924 ns / 0 B | 57.3971 ns / 120 B | 36.4736 ns / 80 B |
+| Empty subscribe | 2.6250 ns / 40 B | 49.4400 ns / 96 B | 31.7660 ns / 56 B |
+| Range subscribe | 53.4814 ns / 96 B | 2,661.3499 ns / 2,472 B | 74.1203 ns / 80 B |
+| Repeat subscribe | 7.4801 ns / 0 B | 2,561.7627 ns / 2,408 B | 72.9776 ns / 80 B |
+| Throw subscribe | 65.5047 ns / 120 B | 121.8558 ns / 240 B | 101.9381 ns / 200 B |
+| FromEnumerable subscribe | 53.8412 ns / 40 B | 2,463.3475 ns / 2,504 B | 79.5410 ns / 88 B |
+| Completed task bridge | 14.7929 ns / 88 B | 1,069.5220 ns / 793 B | 42.9666 ns / 88 B |
+| Create subscribe | 71.6670 ns / 248 B | 50.1209 ns / 168 B | 64.3239 ns / 128 B |
+| CreateSafe subscribe | 58.6071 ns / 248 B | 50.7736 ns / 168 B | 64.0586 ns / 128 B |
+| Defer subscribe | 80.2183 ns / 240 B | 1,494.6925 ns / 1,512 B | 120.5173 ns / 152 B |
+| Start subscribe | 68.4794 ns / 376 B | 884.6336 ns / 751 B | 67.0051 ns / 160 B |
+| Unfold subscribe | 205.0502 ns / 736 B | 2,413.8472 ns / 2,768 B | 106.4548 ns / 128 B |
+| Use subscribe | 78.5460 ns / 432 B | 90.4731 ns / 168 B | 62.4567 ns / 128 B |
+| FromAsyncEnumerable subscribe | 2,572.9176 ns / 2,065 B | 2,042.2704 ns / 2,445 B | 1,375.5498 ns / 1,023 B |
+| Never subscribe/dispose | 0.2897 ns / 0 B | 5.9221 ns / 40 B | 21.0021 ns / 56 B |
+| Map + Keep over range | 144.9399 ns / 208 B | 2,858.9877 ns / 2,616 B | 306.8607 ns / 272 B |
+| Aggregate + Any + Count | 269.0177 ns / 992 B | 5,698.2445 ns / 5,896 B | 625.4828 ns / 1,280 B |
+| StartWith + Append + DefaultIfEmpty | 56.9262 ns / 184 B | 1,055.3664 ns / 1,257 B | 157.0466 ns / 288 B |
+| SelectMany over ranges | 1,054.3650 ns / 712 B | 3,870.5592 ns / 3,872 B | 1,152.8382 ns / 1,040 B |
+| Zip over ranges | 45.4963 ns / 232 B | 3,656.1999 ns / 2,976 B | 747.8750 ns / 656 B |
+| Concat ranges | 77.7447 ns / 256 B | 2,961.9928 ns / 2,856 B | 277.4494 ns / 360 B |
+| Merge ranges | 81.1404 ns / 256 B | 4,211.2274 ns / 3,952 B | 722.6829 ns / 352 B |
+| Race ranges | 42.3265 ns / 192 B | 1,643.5506 ns / 1,760 B | 295.8642 ns / 360 B |
+| Switch ranges | 931.0946 ns / 1,376 B | 2,259.5812 ns / 2,336 B | 789.2157 ns / 392 B |
+| CombineLatest ranges | 118.2750 ns / 504 B | 3,395.1528 ns / 2,824 B | 714.2596 ns / 344 B |
+| WithLatest ranges | 112.4741 ns / 504 B | 3,630.0810 ns / 2,824 B | 409.9192 ns / 248 B |
+| ForkJoin ranges | 82.2519 ns / 480 B | 3,726.2327 ns / 3,136 B | 1,016.8916 ns / 504 B |
+| Delay range | 3,509.9035 ns / 38,816 B | 6,600.1541 ns / 39,584 B | 2,118.7826 ns / 2,200 B |
+| DelayStart range | 1,029.2875 ns / 25,520 B | 2,440.2833 ns / 26,456 B | 349.4229 ns / 552 B |
+| Throttle burst | 3,351.9829 ns / 38,384 B | 2,843.6348 ns / 36,480 B | 1,729.4249 ns / 1,512 B |
+| Sample latest | 1,192.1561 ns / 26,072 B | 2,187.4022 ns / 26,264 B | 388.9601 ns / 664 B |
+| Timestamp range | 452.1057 ns / 312 B | 1,870.3018 ns / 1,608 B | 373.1253 ns / 152 B |
+| TimeInterval range | 542.5902 ns / 736 B | 1,853.5918 ns / 1,712 B | 479.2913 ns / 160 B |
+| Timeout idle | 1,157.1993 ns / 25,912 B | 1,463.0736 ns / 29,776 B | 486.4943 ns / 784 B |
+| ObserveOn immediate | 27.0202 ns / 96 B | 18,308.8684 ns / 11,309 B | 998.1707 ns / 432 B |
+| Replay subscribe | 369.7292 ns / 320 B | 766.3089 ns / 696 B | 461.5158 ns / 688 B |
+| BehaviorSignal 32 values | 613.2438 ns / 176 B | 618.4676 ns / 200 B | 669.5095 ns / 192 B |
+| BehaviorSignal 1024 values | 17,167.4906 ns / 176 B | 16,983.8969 ns / 200 B | 16,967.7836 ns / 192 B |
+| Signal emit, 32 values | 79.8202 ns / 136 B | 105.0434 ns / 136 B | 168.7808 ns / 160 B |
+| Signal emit, 1024 values | 2,512.2955 ns / 136 B | 1,973.1489 ns / 136 B | 2,166.3308 ns / 160 B |
+| Signal subscribe/dispose, 8 observers | 271.5037 ns / 592 B | 340.2902 ns / 1,288 B | 522.6329 ns / 840 B |
+| Signal subscribe/dispose, 64 observers | 3,011.0357 ns / 3,800 B | 4,617.4255 ns / 38,472 B | 4,091.2084 ns / 6,216 B |
+| Publish live connect | 173.3073 ns / 384 B | 3,236.7963 ns / 2,696 B | 561.8089 ns / 368 B |
+| Share live subscribe | 276.6871 ns / 848 B | 3,461.6713 ns / 2,880 B | 548.8631 ns / 488 B |
+| Replay live late subscribe | 712.4733 ns / 568 B | 4,472.9059 ns / 3,408 B | 1,132.5459 ns / 1,360 B |
+| RefCount subscribe | 280.1471 ns / 848 B | 3,155.4108 ns / 2,880 B | 529.4785 ns / 488 B |
+| AutoConnect subscribe | 245.5960 ns / 728 B | 3,474.4862 ns / 2,736 B | 466.0376 ns / 368 B |
+| StateSignal updates | 609.7935 ns / 176 B | 609.6959 ns / 200 B | 662.1553 ns / 192 B |
+| ReadOnlyState projection | 150.0491 ns / 248 B | 106.9365 ns / 328 B | 199.0058 ns / 312 B |
+| TaskSignal subscribe | 6,126.1940 ns / 3,853 B | 874.2303 ns / 886 B | 47.2969 ns / 160 B |
+| Command execute | 144.2864 ns / 600 B | 809.1453 ns / 1,089 B | 123.6877 ns / 296 B |
+| Command result subscribe | 184.0972 ns / 672 B | 46.0881 ns / 136 B | 76.0079 ns / 160 B |
+| CollectList range | 138.5013 ns / 688 B | 2,916.3119 ns / 3,488 B | 189.9245 ns / 632 B |
+| CollectArray range | 114.7686 ns / 656 B | 4,302.7507 ns / 3,640 B | 231.4877 ns / 784 B |
+| CollectArrayAsync range | 37.9519 ns / 384 B | 3,070.6184 ns / 3,984 B | 189.4382 ns / 784 B |
+| FirstAsync range | 7.4257 ns / 56 B | 2,679.5795 ns / 2,792 B | 89.9454 ns / 208 B |
+| ToTask range | 16.8438 ns / 192 B | 2,778.4730 ns / 2,824 B | 104.4256 ns / 208 B |
+| Count(predicate) range | 64.3218 ns / 144 B | 2,804.5286 ns / 2,520 B | 111.1174 ns / 200 B |
+| All + Contains range | 250.2919 ns / 1,024 B | 5,723.2875 ns / 5,816 B | 257.2693 ns / 392 B |
+| Pocket dispose | 85.2006 ns / 408 B | 107.9840 ns / 512 B | 102.8140 ns / 480 B |
+| CurrentThread schedule | 15.6813 ns / 88 B | 18.6513 ns / 88 B | 34.4560 ns / 56 B |
+| Safe witness | 32.4502 ns / 168 B | 16.2257 ns / 136 B | 22.0434 ns / 56 B |
+| Completed Spark | 0.0000 ns / 0 B | 0.0000 ns / 0 B | 0.2869 ns / 0 B |
Performance constraints used by the project:
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 195198c..3a7cbc6 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -16,6 +16,7 @@
+
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/AsyncBridgeBenchmarks.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/AsyncBridgeBenchmarks.cs
index d88b635..03e06f9 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/AsyncBridgeBenchmarks.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/AsyncBridgeBenchmarks.cs
@@ -43,4 +43,16 @@ public int SystemReactiveCompletedTaskBridge()
using var subscription = RxObservable.FromAsync(() => CompletedTask).Subscribe(observer);
return observer.LastValue;
}
+
+ ///
+ /// Completed task conversion in R3.
+ ///
+ /// The emitted value.
+ [Benchmark]
+ public int R3CompletedTaskBridge()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.ToObservable(CompletedTask, configureAwait: false).Subscribe(observer);
+ return observer.LastValue;
+ }
}
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BenchmarkObservers.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BenchmarkObservers.cs
index 0bf8563..94c729d 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BenchmarkObservers.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/BenchmarkObservers.cs
@@ -73,6 +73,11 @@ internal sealed class IntR3Observer : Observer
///
public int NextCount { get; private set; }
+ ///
+ /// Gets the last value observed.
+ ///
+ public int LastValue { get; private set; }
+
///
/// Gets the number of terminal completions observed.
///
@@ -91,6 +96,7 @@ protected override void OnNextCore(int value)
{
NextCount++;
Total += value;
+ LastValue = value;
}
///
@@ -108,6 +114,67 @@ protected override void OnErrorResumeCore(Exception error)
/// The completion result.
protected override void OnCompletedCore(Result result)
{
+ if (result.IsFailure)
+ {
+ ErrorCount++;
+ return;
+ }
+
+ CompletionCount++;
+ }
+}
+
+///
+/// Observer used by R3 benchmark cases that only need an item count.
+///
+/// The observed value type.
+internal sealed class CountingR3Observer : Observer
+{
+ ///
+ /// Gets the number of onNext calls.
+ ///
+ public int Count { get; private set; }
+
+ ///
+ /// Gets the number of terminal completions observed.
+ ///
+ public int CompletionCount { get; private set; }
+
+ ///
+ /// Gets the number of errors observed.
+ ///
+ public int ErrorCount { get; private set; }
+
+ ///
+ /// Called for each emitted value.
+ ///
+ /// The emitted value.
+ protected override void OnNextCore(T value)
+ {
+ Count++;
+ }
+
+ ///
+ /// Called when an error is observed.
+ ///
+ /// The observed exception.
+ protected override void OnErrorResumeCore(Exception error)
+ {
+ ErrorCount++;
+ }
+
+ ///
+ /// Called when sequence completed.
+ ///
+ /// The completion result.
+ protected override void OnCompletedCore(Result result)
+ {
+ if (result.IsFailure)
+ {
+ ErrorCount++;
+ return;
+ }
+
CompletionCount++;
}
}
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ConnectableShareBenchmarks.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ConnectableShareBenchmarks.cs
index d754026..ab8c148 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ConnectableShareBenchmarks.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ConnectableShareBenchmarks.cs
@@ -6,6 +6,8 @@
using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Signals;
+using RxObservable = System.Reactive.Linq.Observable;
+
namespace ReactiveUI.Primitives.Benchmarks;
///
@@ -30,6 +32,34 @@ public int PrimitivesPublishLiveConnect()
return observer.Total;
}
+ ///
+ /// Benchmarks publish connection using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactivePublishLiveConnect()
+ {
+ var observer = new IntSignalObserver();
+ var connectable = RxObservable.Publish(RxObservable.Range(1, Count));
+ using var subscription = connectable.Subscribe(observer);
+ using var connection = connectable.Connect();
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks publish connection using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3PublishLiveConnect()
+ {
+ var observer = new IntR3Observer();
+ var connectable = R3.ObservableExtensions.Publish(R3.Observable.Range(1, Count));
+ using var subscription = connectable.Subscribe(observer);
+ using var connection = connectable.Connect();
+ return observer.Total;
+ }
+
///
/// Benchmarks share-live reference counting.
///
@@ -42,6 +72,31 @@ public int PrimitivesShareLiveSubscribe()
return observer.Total;
}
+ ///
+ /// Benchmarks share/reference counting using System.Reactive publish-refcount.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveShareLiveSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.RefCount(RxObservable.Publish(RxObservable.Range(1, Count)))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks share/reference counting using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3ShareLiveSubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.ObservableExtensions.Share(R3.Observable.Range(1, Count)).Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks replay-live late subscription.
///
@@ -56,6 +111,34 @@ public int PrimitivesReplayLiveLateSubscribe()
return observer.Total;
}
+ ///
+ /// Benchmarks replay late subscription using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveReplayLiveLateSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ var connectable = RxObservable.Replay(RxObservable.Range(1, Count), Count);
+ using var connection = connectable.Connect();
+ using var subscription = connectable.Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks replay late subscription using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3ReplayLiveLateSubscribe()
+ {
+ var observer = new IntR3Observer();
+ var connectable = R3.ObservableExtensions.Replay(R3.Observable.Range(1, Count), Count);
+ using var connection = connectable.Connect();
+ using var subscription = connectable.Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks ref-count subscription.
///
@@ -68,6 +151,33 @@ public int PrimitivesRefCountSubscribe()
return observer.Total;
}
+ ///
+ /// Benchmarks ref-count subscription using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveRefCountSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.RefCount(RxObservable.Publish(RxObservable.Range(1, Count)))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks ref-count subscription using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3RefCountSubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.ObservableExtensions.RefCount(
+ R3.ObservableExtensions.Publish(R3.Observable.Range(1, Count)))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks auto-connect subscription.
///
@@ -79,4 +189,31 @@ public int PrimitivesAutoConnectSubscribe()
using var subscription = Signal.Range(1, Count).PublishLive().AutoConnect().Subscribe(observer);
return observer.Total;
}
+
+ ///
+ /// Benchmarks auto-connect subscription using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveAutoConnectSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.AutoConnect(RxObservable.Publish(RxObservable.Range(1, Count)))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks auto-connect-equivalent subscription using R3 publish/connect.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3AutoConnectSubscribe()
+ {
+ var observer = new IntR3Observer();
+ var connectable = R3.ObservableExtensions.Publish(R3.Observable.Range(1, Count));
+ using var subscription = connectable.Subscribe(observer);
+ using var connection = connectable.Connect();
+ return observer.Total;
+ }
}
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CoreRuntimeBenchmarks.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CoreRuntimeBenchmarks.cs
index 6787031..19beaf9 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CoreRuntimeBenchmarks.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/CoreRuntimeBenchmarks.cs
@@ -56,6 +56,23 @@ public int SystemReactiveCompositeDispose()
return disposed;
}
+ ///
+ /// Composite disposable dispose path in R3.
+ ///
+ /// The number of disposal callbacks executed.
+ [Benchmark]
+ public int R3CompositeDispose()
+ {
+ var disposed = 0;
+ var pocket = new R3.CompositeDisposable(
+ R3.Disposable.Create(() => disposed++),
+ R3.Disposable.Create(() => disposed++),
+ R3.Disposable.Create(() => disposed++));
+
+ pocket.Dispose();
+ return disposed;
+ }
+
///
/// Schedule and execute one action on current-thread sequencer.
///
@@ -80,6 +97,18 @@ public int SystemReactiveCurrentThreadSchedule()
return value;
}
+ ///
+ /// Immediate dispatch through R3 return subscription.
+ ///
+ /// The executed marker value.
+ [Benchmark]
+ public int R3CurrentThreadSchedule()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.Return(1).Subscribe(observer);
+ return observer.LastValue;
+ }
+
///
/// Wrap a witness with the safe witness helper.
///
@@ -94,6 +123,33 @@ public int PrimitivesSafeWitness()
return value;
}
+ ///
+ /// Notify a System.Reactive observer created from delegates.
+ ///
+ /// The forwarded value.
+ [Benchmark]
+ public int SystemReactiveSafeWitness()
+ {
+ var value = 0;
+ var observer = System.Reactive.Observer.Create(x => value = x, _ => { }, () => { });
+ observer.OnNext(42);
+ observer.OnCompleted();
+ return value;
+ }
+
+ ///
+ /// Notify an R3 observer.
+ ///
+ /// The forwarded value.
+ [Benchmark]
+ public int R3SafeWitness()
+ {
+ var observer = new IntR3Observer();
+ observer.OnNext(42);
+ observer.OnCompleted(R3.Result.Success);
+ return observer.LastValue;
+ }
+
///
/// Allocating a completed spark should remain allocation efficient.
///
@@ -104,4 +160,26 @@ public int PrimitivesCompletedSpark()
var spark = Spark.CreateOnCompleted();
return (int)spark.Kind;
}
+
+ ///
+ /// Allocating a completed notification with System.Reactive.
+ ///
+ /// An integer marker extracted from kind.
+ [Benchmark]
+ public int SystemReactiveCompletedSpark()
+ {
+ var notification = System.Reactive.Notification.CreateOnCompleted();
+ return (int)notification.Kind;
+ }
+
+ ///
+ /// Allocating a completed notification with R3.
+ ///
+ /// An integer marker extracted from kind.
+ [Benchmark]
+ public int R3CompletedSpark()
+ {
+ var notification = new R3.Notification(R3.Result.Success);
+ return (int)notification.Kind;
+ }
}
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/FactoryAdapterExpansionBenchmarks.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/FactoryAdapterExpansionBenchmarks.cs
index cbf5e51..2bd8107 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/FactoryAdapterExpansionBenchmarks.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/FactoryAdapterExpansionBenchmarks.cs
@@ -3,11 +3,18 @@
// See the LICENSE file in the project root for full license information.
using System.Collections.Generic;
+using System.Reactive.Concurrency;
+using System.Reactive.Linq;
+using System.Reactive.Threading.Tasks;
+using System.Threading;
using BenchmarkDotNet.Attributes;
using ReactiveUI.Primitives.Concurrency;
using ReactiveUI.Primitives.Disposables;
using ReactiveUI.Primitives.Signals;
+using RxDisposable = System.Reactive.Disposables.Disposable;
+using RxObservable = System.Reactive.Linq.Observable;
+
namespace ReactiveUI.Primitives.Benchmarks;
///
@@ -36,6 +43,40 @@ public int PrimitivesCreateSubscribe()
return observer.Total;
}
+ ///
+ /// Benchmarks a custom create observable using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveCreateSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Create(target =>
+ {
+ target.OnNext(Value);
+ target.OnCompleted();
+ return RxDisposable.Empty;
+ }).Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks a custom create observable using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3CreateSubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.Create(static (R3.Observer target) =>
+ {
+ target.OnNext(Value);
+ target.OnCompleted(R3.Result.Success);
+ return R3.Disposable.Empty;
+ }).Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks a custom safe-create signal.
///
@@ -53,6 +94,40 @@ public int PrimitivesCreateSafeSubscribe()
return observer.Total;
}
+ ///
+ /// Benchmarks the safe custom create path using System.Reactive create semantics.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveCreateSafeSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Create(target =>
+ {
+ target.OnNext(Value);
+ target.OnCompleted();
+ return RxDisposable.Empty;
+ }).Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks the safe custom create path using R3 create semantics.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3CreateSafeSubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.Create(static (R3.Observer target) =>
+ {
+ target.OnNext(Value);
+ target.OnCompleted(R3.Result.Success);
+ return R3.Disposable.Empty;
+ }).Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks deferred factory creation.
///
@@ -65,6 +140,30 @@ public int PrimitivesDeferSubscribe()
return observer.Total;
}
+ ///
+ /// Benchmarks deferred factory creation using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveDeferSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Defer(static () => RxObservable.Range(1, Count)).Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks deferred factory creation using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3DeferSubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.Defer(static () => R3.Observable.Range(1, Count)).Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks starting work on the immediate scheduler.
///
@@ -77,6 +176,31 @@ public int PrimitivesStartSubscribe()
return observer.Total;
}
+ ///
+ /// Benchmarks starting work on the immediate scheduler using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveStartSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Start(static () => Value, ImmediateScheduler.Instance).Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks starting completed work using R3 async factory semantics.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3StartSubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.FromAsync(static _ => new ValueTask(Value), configureAwait: false)
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks finite unfold generation.
///
@@ -90,6 +214,44 @@ public int PrimitivesUnfoldSubscribe()
return observer.Total;
}
+ ///
+ /// Benchmarks finite generation using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveUnfoldSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Generate(
+ 0,
+ static state => state < Count,
+ static state => state + 1,
+ static state => state)
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks finite generation using an R3 create loop.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3UnfoldSubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.Create(static (R3.Observer target) =>
+ {
+ for (var state = 0; state < Count; state++)
+ {
+ target.OnNext(state);
+ }
+
+ target.OnCompleted(R3.Result.Success);
+ return R3.Disposable.Empty;
+ }).Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks resource-scoped signal creation.
///
@@ -102,6 +264,37 @@ public int PrimitivesUseSubscribe()
return observer.Total;
}
+ ///
+ /// Benchmarks resource-scoped observable creation using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveUseSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Using(static () => RxDisposable.Empty, static _ => RxObservable.Return(Value))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks resource-scoped observable creation using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3UseSubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.Create(static (R3.Observer target) =>
+ {
+ using var resource = R3.Disposable.Empty;
+ target.OnNext(Value);
+ target.OnCompleted(R3.Result.Success);
+ return R3.Disposable.Empty;
+ }).Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks async-enumerable adaptation.
///
@@ -112,6 +305,41 @@ public async Task PrimitivesFromAsyncEnumerableSubscribeAsync()
return (await Signal.FromAsyncEnumerable(ValuesAsync()).CollectArrayAsync().ConfigureAwait(false)).Length;
}
+ ///
+ /// Benchmarks async-enumerable adaptation using System.Reactive create semantics.
+ ///
+ /// The number of values collected.
+ [Benchmark]
+ public async Task SystemReactiveFromAsyncEnumerableSubscribeAsync()
+ {
+ var values = await RxObservable.Create(static async (target, cancellationToken) =>
+ {
+ await foreach (var value in ValuesAsync().WithCancellation(cancellationToken).ConfigureAwait(false))
+ {
+ target.OnNext(value);
+ }
+
+ target.OnCompleted();
+ })
+ .ToArray()
+ .ToTask()
+ .ConfigureAwait(false);
+ return values.Length;
+ }
+
+ ///
+ /// Benchmarks async-enumerable adaptation using R3.
+ ///
+ /// The number of values collected.
+ [Benchmark]
+ public async Task R3FromAsyncEnumerableSubscribeAsync()
+ {
+ return (await R3.ObservableExtensions.ToArrayAsync(
+ R3.Observable.ToObservable(ValuesAsync()),
+ CancellationToken.None)
+ .ConfigureAwait(false)).Length;
+ }
+
///
/// Benchmarks subscribing and disposing a never-ending signal.
///
@@ -124,6 +352,30 @@ public int PrimitivesNeverSubscribeDispose()
return observer.NextCount + observer.CompletionCount + observer.ErrorCount;
}
+ ///
+ /// Benchmarks subscribing and disposing a never-ending System.Reactive observable.
+ ///
+ /// The observed notification count.
+ [Benchmark]
+ public int SystemReactiveNeverSubscribeDispose()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Never().Subscribe(observer);
+ return observer.NextCount + observer.CompletionCount + observer.ErrorCount;
+ }
+
+ ///
+ /// Benchmarks subscribing and disposing a never-ending R3 observable.
+ ///
+ /// The observed notification count.
+ [Benchmark]
+ public int R3NeverSubscribeDispose()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.Never().Subscribe(observer);
+ return observer.NextCount + observer.CompletionCount + observer.ErrorCount;
+ }
+
private static async IAsyncEnumerable ValuesAsync()
{
await Task.Yield();
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/OperatorHigherOrderBenchmarks.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/OperatorHigherOrderBenchmarks.cs
index 4b9399a..adee584 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/OperatorHigherOrderBenchmarks.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/OperatorHigherOrderBenchmarks.cs
@@ -5,6 +5,9 @@
using BenchmarkDotNet.Attributes;
using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Signals;
+using System.Threading;
+
+using RxObservable = System.Reactive.Linq.Observable;
namespace ReactiveUI.Primitives.Benchmarks;
@@ -28,6 +31,32 @@ public int PrimitivesConcatRanges()
return observer.Total;
}
+ ///
+ /// Benchmarks concatenating ranges using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveConcatRanges()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Concat(RxObservable.Range(1, Count), RxObservable.Range(1, Count))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks concatenating ranges using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3ConcatRanges()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.Concat(R3.Observable.Range(1, Count), R3.Observable.Range(1, Count))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks merging inner ranges.
///
@@ -40,6 +69,32 @@ public int PrimitivesMergeRanges()
return observer.Total;
}
+ ///
+ /// Benchmarks merging ranges using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveMergeRanges()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Merge(RxObservable.Range(1, Count), RxObservable.Range(1, Count))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks merging ranges using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3MergeRanges()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.Merge(R3.Observable.Range(1, Count), R3.Observable.Range(1, Count))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks racing two sources.
///
@@ -52,6 +107,32 @@ public int PrimitivesRaceRanges()
return observer.Total;
}
+ ///
+ /// Benchmarks racing two sources using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveRaceRanges()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Amb(RxObservable.Range(1, Count), RxObservable.Range(100, Count))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks racing two sources using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3RaceRanges()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.Race(R3.Observable.Range(1, Count), R3.Observable.Range(100, Count))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks switching to the latest inner source.
///
@@ -66,6 +147,36 @@ public int PrimitivesSwitchRanges()
return observer.Total;
}
+ ///
+ /// Benchmarks switching to the latest inner source using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveSwitchRanges()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Switch(
+ RxObservable.ToObservable(new[] { RxObservable.Range(1, Count), RxObservable.Range(100, Count) }))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks switching to the latest inner source using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3SwitchRanges()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.ObservableExtensions.Switch(
+ R3.Observable.ToObservable(
+ new[] { R3.Observable.Range(1, Count), R3.Observable.Range(100, Count) },
+ CancellationToken.None))
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks combine-latest over ranges.
///
@@ -81,6 +192,36 @@ public int PrimitivesCombineLatestRanges()
return observer.Total;
}
+ ///
+ /// Benchmarks combine-latest over ranges using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveCombineLatestRanges()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.CombineLatest(
+ RxObservable.Range(1, Count),
+ RxObservable.Range(10, Count),
+ static (left, right) => left + right).Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks combine-latest over ranges using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3CombineLatestRanges()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.CombineLatest(
+ R3.Observable.Range(1, Count),
+ R3.Observable.Range(10, Count),
+ static (int left, int right) => left + right).Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks with-latest over ranges.
///
@@ -95,6 +236,36 @@ public int PrimitivesWithLatestRanges()
return observer.Total;
}
+ ///
+ /// Benchmarks with-latest over ranges using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveWithLatestRanges()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.WithLatestFrom(
+ RxObservable.Range(1, Count),
+ RxObservable.Range(10, Count),
+ static (left, right) => left + right).Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks with-latest over ranges using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3WithLatestRanges()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.ObservableExtensions.WithLatestFrom(
+ R3.Observable.Range(1, Count),
+ R3.Observable.Range(10, Count),
+ static (int left, int right) => left + right).Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks fork-join over ranges.
///
@@ -109,4 +280,40 @@ public int PrimitivesForkJoinRanges()
static (left, right) => left + right).Subscribe(observer);
return observer.Total;
}
+
+ ///
+ /// Benchmarks fork-join over ranges using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveForkJoinRanges()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.TakeLast(
+ RxObservable.CombineLatest(
+ RxObservable.Range(1, Count),
+ RxObservable.Range(10, Count),
+ static (left, right) => left + right),
+ 1)
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks fork-join-equivalent last combined value over ranges using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3ForkJoinRanges()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.ObservableExtensions.TakeLast(
+ R3.Observable.CombineLatest(
+ R3.Observable.Range(1, Count),
+ R3.Observable.Range(10, Count),
+ static (int left, int right) => left + right),
+ 1)
+ .Subscribe(observer);
+ return observer.Total;
+ }
}
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/OperatorTimeSchedulerBenchmarks.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/OperatorTimeSchedulerBenchmarks.cs
index 5b9e8c4..89547e2 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/OperatorTimeSchedulerBenchmarks.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/OperatorTimeSchedulerBenchmarks.cs
@@ -3,9 +3,15 @@
// See the LICENSE file in the project root for full license information.
using BenchmarkDotNet.Attributes;
+using Microsoft.Extensions.Time.Testing;
using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Concurrency;
using ReactiveUI.Primitives.Signals;
+using System.Reactive.Concurrency;
+using System.Reactive.Linq;
+
+using RxObservable = System.Reactive.Linq.Observable;
+using RxSubject = System.Reactive.Subjects.Subject;
namespace ReactiveUI.Primitives.Benchmarks;
@@ -31,6 +37,38 @@ public int PrimitivesDelayRange()
return observer.Total;
}
+ ///
+ /// Benchmarks delayed range delivery using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveDelayRange()
+ {
+ var scheduler = new HistoricalScheduler();
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Range(1, Count).Delay(TimeSpan.FromTicks(1), scheduler).Subscribe(observer);
+ scheduler.AdvanceBy(TimeSpan.FromTicks(1));
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks delayed range delivery using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3DelayRange()
+ {
+ var timeProvider = new FakeTimeProvider();
+ var observer = new IntR3Observer();
+ using var subscription = R3.ObservableExtensions.Delay(
+ R3.Observable.Range(1, Count),
+ TimeSpan.FromTicks(1),
+ timeProvider)
+ .Subscribe(observer);
+ timeProvider.Advance(TimeSpan.FromTicks(1));
+ return observer.Total;
+ }
+
///
/// Benchmarks delayed subscription.
///
@@ -45,6 +83,40 @@ public int PrimitivesDelayStartRange()
return observer.Total;
}
+ ///
+ /// Benchmarks delayed subscription using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveDelayStartRange()
+ {
+ var scheduler = new HistoricalScheduler();
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Range(1, Count)
+ .DelaySubscription(TimeSpan.FromTicks(1), scheduler)
+ .Subscribe(observer);
+ scheduler.AdvanceBy(TimeSpan.FromTicks(1));
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks delayed subscription using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3DelayStartRange()
+ {
+ var timeProvider = new FakeTimeProvider();
+ var observer = new IntR3Observer();
+ using var subscription = R3.ObservableExtensions.DelaySubscription(
+ R3.Observable.Range(1, Count),
+ TimeSpan.FromTicks(1),
+ timeProvider)
+ .Subscribe(observer);
+ timeProvider.Advance(TimeSpan.FromTicks(1));
+ return observer.Total;
+ }
+
///
/// Benchmarks throttle over a burst.
///
@@ -65,6 +137,47 @@ public int PrimitivesThrottleBurst()
return observer.LastValue;
}
+ ///
+ /// Benchmarks throttle over a burst using System.Reactive.
+ ///
+ /// The last observed value.
+ [Benchmark]
+ public int SystemReactiveThrottleBurst()
+ {
+ var scheduler = new HistoricalScheduler();
+ var observer = new IntSignalObserver();
+ using var source = new RxSubject();
+ using var subscription = source.Throttle(TimeSpan.FromTicks(1), scheduler).Subscribe(observer);
+ for (var i = 0; i < Count; i++)
+ {
+ source.OnNext(i);
+ }
+
+ scheduler.AdvanceBy(TimeSpan.FromTicks(1));
+ return observer.LastValue;
+ }
+
+ ///
+ /// Benchmarks debounce over a burst using R3.
+ ///
+ /// The last observed value.
+ [Benchmark]
+ public int R3ThrottleBurst()
+ {
+ var timeProvider = new FakeTimeProvider();
+ var observer = new IntR3Observer();
+ using var source = new R3.Subject();
+ using var subscription = R3.ObservableExtensions.Debounce(source, TimeSpan.FromTicks(1), timeProvider)
+ .Subscribe(observer);
+ for (var i = 0; i < Count; i++)
+ {
+ source.OnNext(i);
+ }
+
+ timeProvider.Advance(TimeSpan.FromTicks(1));
+ return observer.LastValue;
+ }
+
///
/// Benchmarks sampling the latest value.
///
@@ -81,6 +194,39 @@ public int PrimitivesSampleLatest()
return observer.Total;
}
+ ///
+ /// Benchmarks sampling the latest value using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveSampleLatest()
+ {
+ var scheduler = new HistoricalScheduler();
+ var observer = new IntSignalObserver();
+ using var source = new RxSubject();
+ using var subscription = source.Sample(TimeSpan.FromTicks(1), scheduler).Subscribe(observer);
+ source.OnNext(Count);
+ scheduler.AdvanceBy(TimeSpan.FromTicks(1));
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks sampling the latest value using R3 throttle-last semantics.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3SampleLatest()
+ {
+ var timeProvider = new FakeTimeProvider();
+ var observer = new IntR3Observer();
+ using var source = new R3.Subject();
+ using var subscription = R3.ObservableExtensions.ThrottleLast(source, TimeSpan.FromTicks(1), timeProvider)
+ .Subscribe(observer);
+ source.OnNext(Count);
+ timeProvider.Advance(TimeSpan.FromTicks(1));
+ return observer.Total;
+ }
+
///
/// Benchmarks timestamp projection.
///
@@ -93,6 +239,30 @@ public int PrimitivesTimestampRange()
return count;
}
+ ///
+ /// Benchmarks timestamp projection using System.Reactive.
+ ///
+ /// The number of timestamps observed.
+ [Benchmark]
+ public int SystemReactiveTimestampRange()
+ {
+ var count = 0;
+ using var subscription = RxObservable.Range(1, Count).Timestamp(ImmediateScheduler.Instance).Subscribe(_ => count++);
+ return count;
+ }
+
+ ///
+ /// Benchmarks timestamp projection using R3.
+ ///
+ /// The number of timestamps observed.
+ [Benchmark]
+ public int R3TimestampRange()
+ {
+ var observer = new CountingR3Observer<(long Timestamp, int Value)>();
+ using var subscription = R3.ObservableExtensions.Timestamp(R3.Observable.Range(1, Count)).Subscribe(observer);
+ return observer.Count;
+ }
+
///
/// Benchmarks time-interval projection.
///
@@ -106,19 +276,83 @@ public int PrimitivesTimeIntervalRange()
}
///
- /// Benchmarks timeout error delivery.
+ /// Benchmarks time-interval projection using System.Reactive.
+ ///
+ /// The number of intervals observed.
+ [Benchmark]
+ public int SystemReactiveTimeIntervalRange()
+ {
+ var count = 0;
+ using var subscription = RxObservable.Range(1, Count)
+ .TimeInterval(ImmediateScheduler.Instance)
+ .Subscribe(_ => count++);
+ return count;
+ }
+
+ ///
+ /// Benchmarks time-interval projection using R3.
+ ///
+ /// The number of intervals observed.
+ [Benchmark]
+ public int R3TimeIntervalRange()
+ {
+ var observer = new CountingR3Observer<(TimeSpan Interval, int Value)>();
+ using var subscription = R3.ObservableExtensions.TimeInterval(R3.Observable.Range(1, Count)).Subscribe(observer);
+ return observer.Count;
+ }
+
+ ///
+ /// Benchmarks timeout error delivery after a source becomes idle.
///
/// The number of timeout errors observed.
[Benchmark]
- public int PrimitivesTimeoutNever()
+ public int PrimitivesTimeoutIdle()
{
var clock = new TestClock();
var observer = new IntSignalObserver();
- using var subscription = Signal.Never().Timeout(TimeSpan.FromTicks(1), clock).Subscribe(observer);
+ using var source = new Signal();
+ using var subscription = source.Timeout(TimeSpan.FromTicks(1), clock).Subscribe(observer);
+ source.OnNext(0);
clock.AdvanceBy(TimeSpan.FromTicks(1));
return observer.ErrorCount;
}
+ ///
+ /// Benchmarks timeout error delivery after a source becomes idle using System.Reactive.
+ ///
+ /// The number of timeout errors observed.
+ [Benchmark]
+ public int SystemReactiveTimeoutIdle()
+ {
+ var scheduler = new HistoricalScheduler();
+ var observer = new IntSignalObserver();
+ using var source = new RxSubject();
+ using var subscription = source.Timeout(TimeSpan.FromTicks(1), scheduler).Subscribe(observer);
+ source.OnNext(0);
+ scheduler.AdvanceBy(TimeSpan.FromTicks(1));
+ return observer.ErrorCount;
+ }
+
+ ///
+ /// Benchmarks timeout error delivery after a source becomes idle using R3.
+ ///
+ /// The number of timeout errors observed.
+ [Benchmark]
+ public int R3TimeoutIdle()
+ {
+ var timeProvider = new FakeTimeProvider();
+ var observer = new IntR3Observer();
+ using var source = new R3.Subject();
+ using var subscription = R3.ObservableExtensions.Timeout(
+ source,
+ TimeSpan.FromTicks(1),
+ timeProvider)
+ .Subscribe(observer);
+ source.OnNext(0);
+ timeProvider.Advance(TimeSpan.FromTicks(1));
+ return observer.ErrorCount;
+ }
+
///
/// Benchmarks immediate observe-on dispatch.
///
@@ -130,4 +364,46 @@ public int PrimitivesObserveOnImmediate()
using var subscription = Signal.Range(1, Count).ObserveOn(Sequencer.Immediate).Subscribe(observer);
return observer.Total;
}
+
+ ///
+ /// Benchmarks immediate observe-on dispatch using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveObserveOnImmediate()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Range(1, Count).ObserveOn(ImmediateScheduler.Instance).Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks immediate observe-on dispatch using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3ObserveOnImmediate()
+ {
+ var observer = new IntR3Observer();
+ using var context = new ImmediateSynchronizationContext();
+ using var subscription = R3.ObservableExtensions.ObserveOn(R3.Observable.Range(1, Count), context).Subscribe(observer);
+ return observer.Total;
+ }
+
+ private sealed class ImmediateSynchronizationContext : SynchronizationContext, IDisposable
+ {
+ public override void Post(SendOrPostCallback d, object? state)
+ {
+ d(state);
+ }
+
+ public override void Send(SendOrPostCallback d, object? state)
+ {
+ d(state);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
}
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/Program.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/Program.cs
index 6cff779..befcfb1 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/Program.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/Program.cs
@@ -109,10 +109,12 @@ private static async Task RunSmokeBenchmarksAsync()
var replay = new ReplaySignalBenchmarks();
Console.WriteLine($"PrimitivesReplaySubscribe={replay.PrimitivesReplaySubscribe()}");
Console.WriteLine($"SystemReactiveReplaySubscribe={replay.SystemReactiveReplaySubscribe()}");
+ Console.WriteLine($"R3ReplaySubscribe={replay.R3ReplaySubscribe()}");
var taskBridge = new AsyncBridgeBenchmarks();
Console.WriteLine($"PrimitivesCompletedTaskBridge={taskBridge.PrimitivesCompletedTaskBridge()}");
Console.WriteLine($"SystemReactiveCompletedTaskBridge={taskBridge.SystemReactiveCompletedTaskBridge()}");
+ Console.WriteLine($"R3CompletedTaskBridge={taskBridge.R3CompletedTaskBridge()}");
await RunExpansionSmokeBenchmarksAsync();
@@ -123,34 +125,140 @@ private static async Task RunExpansionSmokeBenchmarksAsync()
{
var factoryAdapters = new FactoryAdapterExpansionBenchmarks();
Console.WriteLine($"PrimitivesCreateSubscribe={factoryAdapters.PrimitivesCreateSubscribe()}");
+ Console.WriteLine($"SystemReactiveCreateSubscribe={factoryAdapters.SystemReactiveCreateSubscribe()}");
+ Console.WriteLine($"R3CreateSubscribe={factoryAdapters.R3CreateSubscribe()}");
+ Console.WriteLine($"PrimitivesCreateSafeSubscribe={factoryAdapters.PrimitivesCreateSafeSubscribe()}");
+ Console.WriteLine($"SystemReactiveCreateSafeSubscribe={factoryAdapters.SystemReactiveCreateSafeSubscribe()}");
+ Console.WriteLine($"R3CreateSafeSubscribe={factoryAdapters.R3CreateSafeSubscribe()}");
Console.WriteLine($"PrimitivesDeferSubscribe={factoryAdapters.PrimitivesDeferSubscribe()}");
+ Console.WriteLine($"SystemReactiveDeferSubscribe={factoryAdapters.SystemReactiveDeferSubscribe()}");
+ Console.WriteLine($"R3DeferSubscribe={factoryAdapters.R3DeferSubscribe()}");
+ Console.WriteLine($"PrimitivesStartSubscribe={factoryAdapters.PrimitivesStartSubscribe()}");
+ Console.WriteLine($"SystemReactiveStartSubscribe={factoryAdapters.SystemReactiveStartSubscribe()}");
+ Console.WriteLine($"R3StartSubscribe={factoryAdapters.R3StartSubscribe()}");
+ Console.WriteLine($"PrimitivesUnfoldSubscribe={factoryAdapters.PrimitivesUnfoldSubscribe()}");
+ Console.WriteLine($"SystemReactiveUnfoldSubscribe={factoryAdapters.SystemReactiveUnfoldSubscribe()}");
+ Console.WriteLine($"R3UnfoldSubscribe={factoryAdapters.R3UnfoldSubscribe()}");
+ Console.WriteLine($"PrimitivesUseSubscribe={factoryAdapters.PrimitivesUseSubscribe()}");
+ Console.WriteLine($"SystemReactiveUseSubscribe={factoryAdapters.SystemReactiveUseSubscribe()}");
+ Console.WriteLine($"R3UseSubscribe={factoryAdapters.R3UseSubscribe()}");
Console.WriteLine(
$"PrimitivesFromAsyncEnumerableSubscribe={await factoryAdapters.PrimitivesFromAsyncEnumerableSubscribeAsync()}");
+ Console.WriteLine(
+ $"SystemReactiveFromAsyncEnumerableSubscribe={await factoryAdapters.SystemReactiveFromAsyncEnumerableSubscribeAsync()}");
+ Console.WriteLine(
+ $"R3FromAsyncEnumerableSubscribe={await factoryAdapters.R3FromAsyncEnumerableSubscribeAsync()}");
+ Console.WriteLine($"PrimitivesNeverSubscribeDispose={factoryAdapters.PrimitivesNeverSubscribeDispose()}");
+ Console.WriteLine($"SystemReactiveNeverSubscribeDispose={factoryAdapters.SystemReactiveNeverSubscribeDispose()}");
+ Console.WriteLine($"R3NeverSubscribeDispose={factoryAdapters.R3NeverSubscribeDispose()}");
var timeSchedulers = new OperatorTimeSchedulerBenchmarks();
Console.WriteLine($"PrimitivesDelayRange={timeSchedulers.PrimitivesDelayRange()}");
+ Console.WriteLine($"SystemReactiveDelayRange={timeSchedulers.SystemReactiveDelayRange()}");
+ Console.WriteLine($"R3DelayRange={timeSchedulers.R3DelayRange()}");
+ Console.WriteLine($"PrimitivesDelayStartRange={timeSchedulers.PrimitivesDelayStartRange()}");
+ Console.WriteLine($"SystemReactiveDelayStartRange={timeSchedulers.SystemReactiveDelayStartRange()}");
+ Console.WriteLine($"R3DelayStartRange={timeSchedulers.R3DelayStartRange()}");
Console.WriteLine($"PrimitivesThrottleBurst={timeSchedulers.PrimitivesThrottleBurst()}");
- Console.WriteLine($"PrimitivesTimeoutNever={timeSchedulers.PrimitivesTimeoutNever()}");
+ Console.WriteLine($"SystemReactiveThrottleBurst={timeSchedulers.SystemReactiveThrottleBurst()}");
+ Console.WriteLine($"R3ThrottleBurst={timeSchedulers.R3ThrottleBurst()}");
+ Console.WriteLine($"PrimitivesSampleLatest={timeSchedulers.PrimitivesSampleLatest()}");
+ Console.WriteLine($"SystemReactiveSampleLatest={timeSchedulers.SystemReactiveSampleLatest()}");
+ Console.WriteLine($"R3SampleLatest={timeSchedulers.R3SampleLatest()}");
+ Console.WriteLine($"PrimitivesTimestampRange={timeSchedulers.PrimitivesTimestampRange()}");
+ Console.WriteLine($"SystemReactiveTimestampRange={timeSchedulers.SystemReactiveTimestampRange()}");
+ Console.WriteLine($"R3TimestampRange={timeSchedulers.R3TimestampRange()}");
+ Console.WriteLine($"PrimitivesTimeIntervalRange={timeSchedulers.PrimitivesTimeIntervalRange()}");
+ Console.WriteLine($"SystemReactiveTimeIntervalRange={timeSchedulers.SystemReactiveTimeIntervalRange()}");
+ Console.WriteLine($"R3TimeIntervalRange={timeSchedulers.R3TimeIntervalRange()}");
+ Console.WriteLine($"PrimitivesTimeoutIdle={timeSchedulers.PrimitivesTimeoutIdle()}");
+ Console.WriteLine($"SystemReactiveTimeoutIdle={timeSchedulers.SystemReactiveTimeoutIdle()}");
+ Console.WriteLine($"R3TimeoutIdle={timeSchedulers.R3TimeoutIdle()}");
+ Console.WriteLine($"PrimitivesObserveOnImmediate={timeSchedulers.PrimitivesObserveOnImmediate()}");
+ Console.WriteLine($"SystemReactiveObserveOnImmediate={timeSchedulers.SystemReactiveObserveOnImmediate()}");
+ Console.WriteLine($"R3ObserveOnImmediate={timeSchedulers.R3ObserveOnImmediate()}");
var higherOrder = new OperatorHigherOrderBenchmarks();
Console.WriteLine($"PrimitivesConcatRanges={higherOrder.PrimitivesConcatRanges()}");
+ Console.WriteLine($"SystemReactiveConcatRanges={higherOrder.SystemReactiveConcatRanges()}");
+ Console.WriteLine($"R3ConcatRanges={higherOrder.R3ConcatRanges()}");
+ Console.WriteLine($"PrimitivesMergeRanges={higherOrder.PrimitivesMergeRanges()}");
+ Console.WriteLine($"SystemReactiveMergeRanges={higherOrder.SystemReactiveMergeRanges()}");
+ Console.WriteLine($"R3MergeRanges={higherOrder.R3MergeRanges()}");
+ Console.WriteLine($"PrimitivesRaceRanges={higherOrder.PrimitivesRaceRanges()}");
+ Console.WriteLine($"SystemReactiveRaceRanges={higherOrder.SystemReactiveRaceRanges()}");
+ Console.WriteLine($"R3RaceRanges={higherOrder.R3RaceRanges()}");
+ Console.WriteLine($"PrimitivesSwitchRanges={higherOrder.PrimitivesSwitchRanges()}");
+ Console.WriteLine($"SystemReactiveSwitchRanges={higherOrder.SystemReactiveSwitchRanges()}");
+ Console.WriteLine($"R3SwitchRanges={higherOrder.R3SwitchRanges()}");
Console.WriteLine($"PrimitivesCombineLatestRanges={higherOrder.PrimitivesCombineLatestRanges()}");
+ Console.WriteLine($"SystemReactiveCombineLatestRanges={higherOrder.SystemReactiveCombineLatestRanges()}");
+ Console.WriteLine($"R3CombineLatestRanges={higherOrder.R3CombineLatestRanges()}");
+ Console.WriteLine($"PrimitivesWithLatestRanges={higherOrder.PrimitivesWithLatestRanges()}");
+ Console.WriteLine($"SystemReactiveWithLatestRanges={higherOrder.SystemReactiveWithLatestRanges()}");
+ Console.WriteLine($"R3WithLatestRanges={higherOrder.R3WithLatestRanges()}");
Console.WriteLine($"PrimitivesForkJoinRanges={higherOrder.PrimitivesForkJoinRanges()}");
+ Console.WriteLine($"SystemReactiveForkJoinRanges={higherOrder.SystemReactiveForkJoinRanges()}");
+ Console.WriteLine($"R3ForkJoinRanges={higherOrder.R3ForkJoinRanges()}");
var terminalCollections = new TerminalCollectionBenchmarks();
Console.WriteLine($"PrimitivesCollectList={terminalCollections.PrimitivesCollectList()}");
+ Console.WriteLine($"SystemReactiveCollectList={terminalCollections.SystemReactiveCollectList()}");
+ Console.WriteLine($"R3CollectList={await terminalCollections.R3CollectList()}");
+ Console.WriteLine($"PrimitivesCollectArray={terminalCollections.PrimitivesCollectArray()}");
+ Console.WriteLine($"SystemReactiveCollectArray={terminalCollections.SystemReactiveCollectArray()}");
+ Console.WriteLine($"R3CollectArray={await terminalCollections.R3CollectArray()}");
+ Console.WriteLine($"PrimitivesCollectArrayAsync={await terminalCollections.PrimitivesCollectArrayAsync()}");
+ Console.WriteLine($"SystemReactiveCollectArrayAsync={await terminalCollections.SystemReactiveCollectArrayAsync()}");
+ Console.WriteLine($"R3CollectArrayAsync={await terminalCollections.R3CollectArrayAsync()}");
Console.WriteLine($"PrimitivesFirstAsync={await terminalCollections.PrimitivesFirstAsync()}");
+ Console.WriteLine($"SystemReactiveFirstAsync={await terminalCollections.SystemReactiveFirstAsync()}");
+ Console.WriteLine($"R3FirstAsync={await terminalCollections.R3FirstAsync()}");
+ Console.WriteLine($"PrimitivesToTask={await terminalCollections.PrimitivesToTask()}");
+ Console.WriteLine($"SystemReactiveToTask={await terminalCollections.SystemReactiveToTask()}");
+ Console.WriteLine($"R3ToTask={await terminalCollections.R3ToTask()}");
+ Console.WriteLine($"PrimitivesCountPredicate={terminalCollections.PrimitivesCountPredicate()}");
+ Console.WriteLine($"SystemReactiveCountPredicate={terminalCollections.SystemReactiveCountPredicate()}");
+ Console.WriteLine($"R3CountPredicate={await terminalCollections.R3CountPredicate()}");
Console.WriteLine($"PrimitivesAllContains={terminalCollections.PrimitivesAllContains()}");
+ Console.WriteLine($"SystemReactiveAllContains={terminalCollections.SystemReactiveAllContains()}");
+ Console.WriteLine($"R3AllContains={await terminalCollections.R3AllContains()}");
var connectableShare = new ConnectableShareBenchmarks();
Console.WriteLine($"PrimitivesPublishLiveConnect={connectableShare.PrimitivesPublishLiveConnect()}");
+ Console.WriteLine($"SystemReactivePublishLiveConnect={connectableShare.SystemReactivePublishLiveConnect()}");
+ Console.WriteLine($"R3PublishLiveConnect={connectableShare.R3PublishLiveConnect()}");
Console.WriteLine($"PrimitivesShareLiveSubscribe={connectableShare.PrimitivesShareLiveSubscribe()}");
+ Console.WriteLine($"SystemReactiveShareLiveSubscribe={connectableShare.SystemReactiveShareLiveSubscribe()}");
+ Console.WriteLine($"R3ShareLiveSubscribe={connectableShare.R3ShareLiveSubscribe()}");
Console.WriteLine($"PrimitivesReplayLiveLateSubscribe={connectableShare.PrimitivesReplayLiveLateSubscribe()}");
+ Console.WriteLine($"SystemReactiveReplayLiveLateSubscribe={connectableShare.SystemReactiveReplayLiveLateSubscribe()}");
+ Console.WriteLine($"R3ReplayLiveLateSubscribe={connectableShare.R3ReplayLiveLateSubscribe()}");
+ Console.WriteLine($"PrimitivesRefCountSubscribe={connectableShare.PrimitivesRefCountSubscribe()}");
+ Console.WriteLine($"SystemReactiveRefCountSubscribe={connectableShare.SystemReactiveRefCountSubscribe()}");
+ Console.WriteLine($"R3RefCountSubscribe={connectableShare.R3RefCountSubscribe()}");
+ Console.WriteLine($"PrimitivesAutoConnectSubscribe={connectableShare.PrimitivesAutoConnectSubscribe()}");
+ Console.WriteLine($"SystemReactiveAutoConnectSubscribe={connectableShare.SystemReactiveAutoConnectSubscribe()}");
+ Console.WriteLine($"R3AutoConnectSubscribe={connectableShare.R3AutoConnectSubscribe()}");
var stateTaskCommand = new StateTaskCommandBenchmarks();
Console.WriteLine($"PrimitivesStateSignalUpdates={stateTaskCommand.PrimitivesStateSignalUpdates()}");
+ Console.WriteLine($"SystemReactiveStateSignalUpdates={stateTaskCommand.SystemReactiveStateSignalUpdates()}");
+ Console.WriteLine($"R3StateSignalUpdates={stateTaskCommand.R3StateSignalUpdates()}");
+ Console.WriteLine($"PrimitivesReadOnlyStateProjection={stateTaskCommand.PrimitivesReadOnlyStateProjection()}");
+ Console.WriteLine(
+ $"SystemReactiveReadOnlyStateProjection={stateTaskCommand.SystemReactiveReadOnlyStateProjection()}");
+ Console.WriteLine($"R3ReadOnlyStateProjection={stateTaskCommand.R3ReadOnlyStateProjection()}");
Console.WriteLine($"PrimitivesTaskSignalSubscribe={stateTaskCommand.PrimitivesTaskSignalSubscribe()}");
+ Console.WriteLine($"SystemReactiveTaskSignalSubscribe={stateTaskCommand.SystemReactiveTaskSignalSubscribe()}");
+ Console.WriteLine($"R3TaskSignalSubscribe={stateTaskCommand.R3TaskSignalSubscribe()}");
Console.WriteLine($"PrimitivesCommandExecute={await stateTaskCommand.PrimitivesCommandExecuteAsync()}");
+ Console.WriteLine($"SystemReactiveCommandExecute={await stateTaskCommand.SystemReactiveCommandExecuteAsync()}");
+ Console.WriteLine($"R3CommandExecute={stateTaskCommand.R3CommandExecute()}");
+ Console.WriteLine(
+ $"PrimitivesCommandResultSubscribe={await stateTaskCommand.PrimitivesCommandResultSubscribeAsync()}");
+ Console.WriteLine($"SystemReactiveCommandResultSubscribe={stateTaskCommand.SystemReactiveCommandResultSubscribe()}");
+ Console.WriteLine($"R3CommandResultSubscribe={stateTaskCommand.R3CommandResultSubscribe()}");
}
private static void RunCoreRuntimeSmokeBenchmarks()
@@ -158,10 +266,16 @@ private static void RunCoreRuntimeSmokeBenchmarks()
var coreRuntime = new CoreRuntimeBenchmarks();
Console.WriteLine($"PrimitivesPocketDispose={coreRuntime.PrimitivesPocketDispose()}");
Console.WriteLine($"SystemReactiveCompositeDispose={coreRuntime.SystemReactiveCompositeDispose()}");
+ Console.WriteLine($"R3CompositeDispose={coreRuntime.R3CompositeDispose()}");
Console.WriteLine($"PrimitivesCurrentThreadSchedule={coreRuntime.PrimitivesCurrentThreadSchedule()}");
Console.WriteLine(
$"SystemReactiveCurrentThreadSchedule={coreRuntime.SystemReactiveCurrentThreadSchedule()}");
+ Console.WriteLine($"R3CurrentThreadSchedule={coreRuntime.R3CurrentThreadSchedule()}");
Console.WriteLine($"PrimitivesSafeWitness={coreRuntime.PrimitivesSafeWitness()}");
+ Console.WriteLine($"SystemReactiveSafeWitness={coreRuntime.SystemReactiveSafeWitness()}");
+ Console.WriteLine($"R3SafeWitness={coreRuntime.R3SafeWitness()}");
Console.WriteLine($"PrimitivesCompletedSpark={coreRuntime.PrimitivesCompletedSpark()}");
+ Console.WriteLine($"SystemReactiveCompletedSpark={coreRuntime.SystemReactiveCompletedSpark()}");
+ Console.WriteLine($"R3CompletedSpark={coreRuntime.R3CompletedSpark()}");
}
}
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReactiveUI.Primitives.Benchmarks.csproj b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReactiveUI.Primitives.Benchmarks.csproj
index 0ebd45b..742431b 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReactiveUI.Primitives.Benchmarks.csproj
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReactiveUI.Primitives.Benchmarks.csproj
@@ -10,11 +10,12 @@
true
true
false
- $(NoWarn);SA1600;SA1649;SA1402;SA1518;SA1208;SA1210;SA1211;S109;S3257;CA1822;S1128;SA1200
+ $(NoWarn);SA1600;SA1649;SA1402;SA1518;SA1208;SA1210;SA1211;S109;S3257;CA1822;S1128;SA1200;S4144;S6966;IDE0300;S138;CA1849
+
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReplaySignalBenchmarks.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReplaySignalBenchmarks.cs
index e201289..5f4a5fc 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReplaySignalBenchmarks.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/ReplaySignalBenchmarks.cs
@@ -7,6 +7,7 @@
using ReactiveUI.Primitives.Signals;
using R3;
+using R3ReplaySubject = R3.ReplaySubject;
using RxReplaySubject = System.Reactive.Subjects.ReplaySubject;
namespace ReactiveUI.Primitives.Benchmarks;
@@ -45,6 +46,20 @@ public int SystemReactiveReplaySubscribe()
return observer.Total;
}
+ ///
+ /// Bounded replay subscription benchmark for R3.
+ ///
+ /// The sum replayed to a late subscriber.
+ [Benchmark]
+ public int R3ReplaySubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var subject = new R3ReplaySubject(16);
+ PopulateReplaySubject(subject);
+ using var subscription = subject.Subscribe(observer);
+ return observer.Total;
+ }
+
private static void PopulateReplaySubject(ReplaySignal subject)
{
for (var i = 0; i < 16; i++)
@@ -60,5 +75,12 @@ private static void PopulateReplaySubject(RxReplaySubject subject)
subject.OnNext(i);
}
}
-}
+ private static void PopulateReplaySubject(R3ReplaySubject subject)
+ {
+ for (var i = 0; i < 16; i++)
+ {
+ subject.OnNext(i);
+ }
+ }
+}
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/StateTaskCommandBenchmarks.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/StateTaskCommandBenchmarks.cs
index 49349ee..020417e 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/StateTaskCommandBenchmarks.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/StateTaskCommandBenchmarks.cs
@@ -6,6 +6,9 @@
using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Concurrency;
using ReactiveUI.Primitives.Signals;
+using System.Reactive.Concurrency;
+using System.Reactive.Linq;
+using System.Reactive.Threading.Tasks;
namespace ReactiveUI.Primitives.Benchmarks;
@@ -36,6 +39,42 @@ public int PrimitivesStateSignalUpdates()
return observer.Total;
}
+ ///
+ /// Benchmarks state updates using System.Reactive behavior subject.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveStateSignalUpdates()
+ {
+ var observer = new IntSignalObserver();
+ using var state = new System.Reactive.Subjects.BehaviorSubject(0);
+ using var subscription = state.Subscribe(observer);
+ for (var i = 0; i < Count; i++)
+ {
+ state.OnNext(i);
+ }
+
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks state updates using R3 behavior subject.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3StateSignalUpdates()
+ {
+ var observer = new IntR3Observer();
+ using var state = new R3.BehaviorSubject(0);
+ using var subscription = state.Subscribe(observer);
+ for (var i = 0; i < Count; i++)
+ {
+ state.OnNext(i);
+ }
+
+ return observer.Total;
+ }
+
///
/// Benchmarks read-only state projection.
///
@@ -49,6 +88,35 @@ public int PrimitivesReadOnlyStateProjection()
return projected.Value;
}
+ ///
+ /// Benchmarks read-only state projection using System.Reactive.
+ ///
+ /// The current projected value.
+ [Benchmark]
+ public int SystemReactiveReadOnlyStateProjection()
+ {
+ var current = 0;
+ using var state = new System.Reactive.Subjects.BehaviorSubject(Value);
+ using var subscription = state.Select(static value => value + 1).Subscribe(value => current = value);
+ state.OnNext(Value + 1);
+ return current;
+ }
+
+ ///
+ /// Benchmarks read-only state projection using R3.
+ ///
+ /// The current projected value.
+ [Benchmark]
+ public int R3ReadOnlyStateProjection()
+ {
+ using var state = new R3.BehaviorSubject(Value);
+ using var projected = R3.ReactivePropertyExtensions.ToReadOnlyReactiveProperty(
+ R3.ObservableExtensions.Select(state, static (int value) => value + 1),
+ Value + 1);
+ state.OnNext(Value + 1);
+ return projected.CurrentValue;
+ }
+
///
/// Benchmarks task signal subscription.
///
@@ -62,6 +130,34 @@ public int PrimitivesTaskSignalSubscribe()
return observer.Total;
}
+ ///
+ /// Benchmarks task-backed observable subscription using System.Reactive.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveTaskSignalSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = System.Reactive.Linq.Observable.FromAsync(
+ static () => Task.FromResult(Value),
+ ImmediateScheduler.Instance)
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks task-backed observable subscription using R3.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3TaskSignalSubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var subscription = R3.Observable.ToObservable(Task.FromResult(Value), configureAwait: false)
+ .Subscribe(observer);
+ return observer.Total;
+ }
+
///
/// Benchmarks command execution.
///
@@ -73,6 +169,27 @@ public async Task PrimitivesCommandExecuteAsync()
return await command.ExecuteAsync().ConfigureAwait(false);
}
+ ///
+ /// Benchmarks command-like execution using System.Reactive async factory semantics.
+ ///
+ /// The command result.
+ [Benchmark]
+ public Task SystemReactiveCommandExecuteAsync() =>
+ System.Reactive.Linq.Observable.Start(static () => Value, ImmediateScheduler.Instance).FirstAsync().ToTask();
+
+ ///
+ /// Benchmarks command execution using R3.
+ ///
+ /// The command result.
+ [Benchmark]
+ public int R3CommandExecute()
+ {
+ var result = 0;
+ using var command = new R3.ReactiveCommand(value => result = value);
+ command.Execute(Value);
+ return result;
+ }
+
///
/// Benchmarks command result publication.
///
@@ -86,4 +203,32 @@ public async Task PrimitivesCommandResultSubscribeAsync()
await command.ExecuteAsync().ConfigureAwait(false);
return observer.Total;
}
+
+ ///
+ /// Benchmarks command-result publication using System.Reactive subject semantics.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int SystemReactiveCommandResultSubscribe()
+ {
+ var observer = new IntSignalObserver();
+ using var results = new System.Reactive.Subjects.Subject();
+ using var subscription = results.Subscribe(observer);
+ results.OnNext(Value);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks command-result publication using R3 subject semantics.
+ ///
+ /// The observed total.
+ [Benchmark]
+ public int R3CommandResultSubscribe()
+ {
+ var observer = new IntR3Observer();
+ using var results = new R3.Subject();
+ using var subscription = results.Subscribe(observer);
+ results.OnNext(Value);
+ return observer.Total;
+ }
}
diff --git a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/TerminalCollectionBenchmarks.cs b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/TerminalCollectionBenchmarks.cs
index a838b78..907b30a 100644
--- a/src/benchmarks/ReactiveUI.Primitives.Benchmarks/TerminalCollectionBenchmarks.cs
+++ b/src/benchmarks/ReactiveUI.Primitives.Benchmarks/TerminalCollectionBenchmarks.cs
@@ -5,6 +5,11 @@
using BenchmarkDotNet.Attributes;
using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Signals;
+using System.Reactive.Linq;
+using System.Reactive.Threading.Tasks;
+using System.Threading;
+
+using RxObservable = System.Reactive.Linq.Observable;
namespace ReactiveUI.Primitives.Benchmarks;
@@ -28,6 +33,29 @@ public int PrimitivesCollectList()
return result;
}
+ ///
+ /// Benchmarks collecting into a list using System.Reactive.
+ ///
+ /// The collected count.
+ [Benchmark]
+ public int SystemReactiveCollectList()
+ {
+ var result = 0;
+ using var subscription = RxObservable.Range(1, Count).ToList().Subscribe(values => result = values.Count);
+ return result;
+ }
+
+ ///
+ /// Benchmarks collecting into a list using R3.
+ ///
+ /// The collected count.
+ [Benchmark]
+ public async Task R3CollectList()
+ {
+ return (await R3.ObservableExtensions.ToListAsync(R3.Observable.Range(1, Count), CancellationToken.None)
+ .ConfigureAwait(false)).Count;
+ }
+
///
/// Benchmarks collecting into an array signal.
///
@@ -40,6 +68,29 @@ public int PrimitivesCollectArray()
return result;
}
+ ///
+ /// Benchmarks collecting into an array using System.Reactive.
+ ///
+ /// The collected count.
+ [Benchmark]
+ public int SystemReactiveCollectArray()
+ {
+ var result = 0;
+ using var subscription = RxObservable.Range(1, Count).ToArray().Subscribe(values => result = values.Length);
+ return result;
+ }
+
+ ///
+ /// Benchmarks collecting into an array using R3.
+ ///
+ /// The collected count.
+ [Benchmark]
+ public async Task R3CollectArray()
+ {
+ return (await R3.ObservableExtensions.ToArrayAsync(R3.Observable.Range(1, Count), CancellationToken.None)
+ .ConfigureAwait(false)).Length;
+ }
+
///
/// Benchmarks asynchronous array collection.
///
@@ -50,6 +101,27 @@ public async Task PrimitivesCollectArrayAsync()
return (await Signal.Range(1, Count).CollectArrayAsync().ConfigureAwait(false)).Length;
}
+ ///
+ /// Benchmarks asynchronous array collection using System.Reactive.
+ ///
+ /// The collected count.
+ [Benchmark]
+ public async Task SystemReactiveCollectArrayAsync()
+ {
+ return (await RxObservable.Range(1, Count).ToArray().ToTask().ConfigureAwait(false)).Length;
+ }
+
+ ///
+ /// Benchmarks asynchronous array collection using R3.
+ ///
+ /// The collected count.
+ [Benchmark]
+ public async Task R3CollectArrayAsync()
+ {
+ return (await R3.ObservableExtensions.ToArrayAsync(R3.Observable.Range(1, Count), CancellationToken.None)
+ .ConfigureAwait(false)).Length;
+ }
+
///
/// Benchmarks first-value task conversion.
///
@@ -58,6 +130,22 @@ public async Task PrimitivesCollectArrayAsync()
public Task PrimitivesFirstAsync() =>
Signal.Range(1, Count).FirstAsync();
+ ///
+ /// Benchmarks first-value task conversion using System.Reactive.
+ ///
+ /// The first value.
+ [Benchmark]
+ public Task SystemReactiveFirstAsync() =>
+ RxObservable.Range(1, Count).FirstAsync().ToTask();
+
+ ///
+ /// Benchmarks first-value task conversion using R3.
+ ///
+ /// The first value.
+ [Benchmark]
+ public Task R3FirstAsync() =>
+ R3.ObservableExtensions.FirstAsync(R3.Observable.Range(1, Count), CancellationToken.None);
+
///
/// Benchmarks last-value task conversion.
///
@@ -66,6 +154,22 @@ public Task PrimitivesFirstAsync() =>
public Task PrimitivesToTask() =>
Signal.Range(1, Count).ToTask();
+ ///
+ /// Benchmarks last-value task conversion using System.Reactive.
+ ///
+ /// The last value.
+ [Benchmark]
+ public Task SystemReactiveToTask() =>
+ RxObservable.Range(1, Count).ToTask();
+
+ ///
+ /// Benchmarks last-value task conversion using R3.
+ ///
+ /// The last value.
+ [Benchmark]
+ public Task R3ToTask() =>
+ R3.ObservableExtensions.LastAsync(R3.Observable.Range(1, Count), CancellationToken.None);
+
///
/// Benchmarks predicate count.
///
@@ -78,6 +182,29 @@ public int PrimitivesCountPredicate()
return observer.Total;
}
+ ///
+ /// Benchmarks predicate count using System.Reactive.
+ ///
+ /// The matching count.
+ [Benchmark]
+ public int SystemReactiveCountPredicate()
+ {
+ var observer = new IntSignalObserver();
+ using var subscription = RxObservable.Range(1, Count).Count(static value => value % 2 == 0).Subscribe(observer);
+ return observer.Total;
+ }
+
+ ///
+ /// Benchmarks predicate count using R3.
+ ///
+ /// The matching count.
+ [Benchmark]
+ public Task R3CountPredicate() =>
+ R3.ObservableExtensions.CountAsync(
+ R3.Observable.Range(1, Count),
+ static (int value) => value % 2 == 0,
+ CancellationToken.None);
+
///
/// Benchmarks all and contains terminal predicates.
///
@@ -90,4 +217,37 @@ public int PrimitivesAllContains()
using var contains = Signal.Range(1, Count).Contains(Count).Subscribe(value => result += value ? 1 : 0);
return result;
}
+
+ ///
+ /// Benchmarks all and contains terminal predicates using System.Reactive.
+ ///
+ /// The number of true results.
+ [Benchmark]
+ public int SystemReactiveAllContains()
+ {
+ var result = 0;
+ using var all = RxObservable.Range(1, Count).All(static value => value > 0).Subscribe(value => result += value ? 1 : 0);
+ using var contains = RxObservable.Range(1, Count).Contains(Count).Subscribe(value => result += value ? 1 : 0);
+ return result;
+ }
+
+ ///
+ /// Benchmarks all and contains terminal predicates using R3.
+ ///
+ /// The number of true results.
+ [Benchmark]
+ public async Task R3AllContains()
+ {
+ var all = await R3.ObservableExtensions.AllAsync(
+ R3.Observable.Range(1, Count),
+ static (int value) => value > 0,
+ CancellationToken.None)
+ .ConfigureAwait(false);
+ var contains = await R3.ObservableExtensions.ContainsAsync(
+ R3.Observable.Range(1, Count),
+ Count,
+ CancellationToken.None)
+ .ConfigureAwait(false);
+ return (all ? 1 : 0) + (contains ? 1 : 0);
+ }
}