-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize for single observer #1121
Comments
cc/ @trxcllnt This optimization is actually made a little trickier by the With lift, this is more difficult because there is one, shared, Observable type that is being used through the operator chain. Another difficulty is that right now, there's no back reference to the Subscriber that made the source Observable. So there's no way to clone it and add the appropriate transformation (filter, map, etc). I think what we'd have to something like
All-in-all, though, I'd worry about adding all of that conditional logic. ... I'm sure there's a better way. |
if this is too off-topic, please let me know and I will create another issue - that said, I was about to use the same title :) I don't understand why the default is cold observables. For instance... const s = new Rx.Subject();
o = s.map(i => {
console.log('side effect ' + i)
return i * 2;
});
o.subscribe(v => console.log(v));
o.subscribe(v => console.log(v));
s.next(2) outputs
But this is not what I would expect. I can fix this by using const s = new Rx.Subject();
o = s.map(i => {
console.log('side effect ' + i)
return i * 2;
}).share();
o.subscribe(v => console.log(v));
o.subscribe(v => console.log(v));
s.next(2) outputs
But why would I need to do that? Given that the Subject is pushing the same input, I should get the same output to functions like I suspect there is either a fundamental RFP reason for this which I have not learned yet, or this is incidental complexity caused by some implementation detail. If it is the latter I would love to find a way to fix that before 5 comes out of beta. In summary, not only do I agree with @benjamingr that we should optimize for one observer, I think that multiple observers should be able to leverage memoized computation for the portion of the observable chain they share. A compromise might be explicit side effects like |
Yes, though I'm not sure I agree this is more difficult with The difficulty I see with But we can add to the Operator type so that the Operator implementations optimize the Subscriber linked list upon subscription. Even though we'd have the same number of intermediate Observables and Operator instances, we'd have fewer Subscriber instances (and thus, hops when notifying), and that's usually where I've seen potential performance problems. That said, what we have today is already significantly improved over current Rx, so much so that I feel comfortable building my idea for a Falcor-integrated UI library on top of it. |
@jhoguet as for your question, "cold" and "hot" don't refer to unicast vs. multicast, they refer to the state of the computation. An Observable represents an asynchronous computation (aka, it's a function that can return multiple values between now and infinity). "Cold" Observables are just like functions which haven't been called (subscribed to) yet. Each time you call it (aka, subscribe to it), you're re-running whatever calculation the Observable performs. "Hot" Observables are just regular cold Observables that you've shoved a Subject between you and the cold Observable source. When you subscribe to it, you're really subscribing to the Subject over and over. Subjects can have any number of Observers (just like EventEmitter, etc.), but the original cold source has only one Observer (the Subject that's sitting between you and the source). When you "fix" your example by using |
@benjamingr you wrote:
Have transducers been considered, for example, with transducers, a chain of map, filter etc. applications would be fused into one application. Analogous with loop fusion. |
@monfera Interesting idea: single observer optimize [at least] the transducer route for hot regions of code where performance matters |
I'm going to close this issue, as it's gone stale. But collapsing operators is still on my radar. |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Continuing my discussion with @Blesh from angular/angular#5876
We're talking about cases like
clicks(myElement).map(fn1).filter(fn2).subscribe(...)
.I wrote there:
I'd expect Rx to optimize this under the hood and not really create a new subscription/observable/observer until it has to (that is, I'd expect it to "forward" the original subscription and then copy on write when a second subscriber is attached). The most common case at least for things like http calls (but definitely not the only one) is to have a single subscriber. I guess I'd expect RxJS to optimize for that (like bluebird does).
@Blesh wrote:
I wrote:
The unicast lazy operation model gives you the ability to aggressively optimize those calls - I think it could be a big win for cases where performance is an issue. I think "expect one subscriber, and copy on write when that assumption fails" would mainly give people who use a new RxJS observable for singular events a big performance boost - and even make it viable
Even asynchronous ones can be composed to a single action in production builds.
I think optimizing for single subscriber could be a big performance win, I think we can safely optimize
flatMap(...).map(...)
intoflatMap(..., ...)
and "bail out" if a second subscriber is added to the original result and similarly for most/all combinators. The single subscriber case is the most common in my code and I assume other code bases are no different - I think it should be optimized aggressively at the expense of multiple subscribers being initially a little slower.The text was updated successfully, but these errors were encountered: