-
-
Notifications
You must be signed in to change notification settings - Fork 192
[Feature]: New higher-performance Filter() operators #758
Description
Describe the functionality desired 🐞
Consider the following new filtering operators, which should provide performance improvements over existing operators:
public static IObservable<IChangeSet<TObject, TKey>> Filter<TObject, TKey, TState>(
this IObservable<IChangeSet<TObject, TKey>> source,
IObservable<TState> state,
Func<TState, TObject, bool> predicate,
bool suppressEmptyChangeSets = true);
public static IObservable<IChangeSet<TItem>> Filter<TItem, TState>(
this IObservable<IChangeSet<TItem>> source,
IObservable<TState> state,
Func<TState, TItem, bool> predicate,
bool suppressEmptyChangeSets = true);
public static IObservable<IChangeSet<TObject, TKey>> FilterValues<TObject, TKey>(
this IObservable<IChangeSet<TObject, TKey>> source,
Func<TObject, bool> predicate,
bool suppressEmptyChangeSets = true);Not set on the name for the third one, but it stems from a conversation with @RolandPheasant and @dwcullop in Slack. Alternatives for this would maybe be .FilterSlim() or .FilterDeterministic() or .FilterWithoutCaching(). Whatever the name, the goal would be to keep it consistent with other operators (like .Transform()) that normally keep an internal items cache, but could have versions that don't.
The steps the functionality will provide
For the first two operators, utilizing an IObservable<TState> parameter, the primary improvement operator is to reduce memory allocations required by existing operators that support dynamic filtering.
The third operator would improve performance for static-filtering scenarios, where the filter predicate is relatively fast, and is known to be deterministic, I.E. collection items are immutable, at least from the perspective of filtering. Under this scenario, .Filter() can be implemented without the need for an internal cache of items, becoming allocation-free for all single-change ChangeSets.
This operator would not be applicable as an ObservableList operator, as using deterministic-filtering does not eliminate the need for an internal items cache. An internal cache is still needed to track item indexes, so downstream indexes can be adjusted for items filtered out of the source.
Considerations
- Consumers can currently use
.Filter()with theIObservable<Func<TObject, bool>>orIObservable<Func<TObject, TKey, bool>>parameters to dynamically control filtering, but this requires either a newFunc<>allocation for each change in filtering rules, or a decent bit of tedious code to reuse and re-emit a sharedFunc<>instance each time, while instead mutating its closure. - Consumers can currently use
.FilterOnObservable(), but this requires multiple object allocations per-added-item, to create anIObservable<bool>that controls filtering of that item, and to subscribe to that stream. This approach also doesn't really support collection items being immutable, at all, as replace operations force a teardown and reconstruction of the filtering stream for that item.