Skip to content

Commit 06f1a25

Browse files
bmeurerbenlesh
authored andcommitted
perf(Subscription): improve parent management (ReactiveX#4526)
Merge the `_parent` and `_parents` fields on the `Subscription` class into a single `_parentOrParents` field, which can hold either `null` (in case of no parents), a `Subscription` instance (in case of a single parent), or an array of `Subscription`s (in case of multiple parents). This not only shrinks the size of `Subscription` (and subclass) instances, but more importantly reduces the number of megamorphic property access sites in the hot `Subscription` methods, especially in `unsubscribe()` and `add()`. Also inline the `Subscription#_addParent()` method into `Subscription#add()`, as it's the only call site anyways and this removes another hot megamorphic `LoadIC` that was only needed to lookup `subscription._addParent` in the call site. Finally remove the `hasErrors` variable from `unsubscribe()` method and instead just check `errors` variable directly.
1 parent a47d38d commit 06f1a25

File tree

2 files changed

+43
-58
lines changed

2 files changed

+43
-58
lines changed

src/internal/Subscriber.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,12 @@ export class Subscriber<T> extends Subscription implements Observer<T> {
151151

152152
/** @deprecated This is an internal implementation detail, do not use. */
153153
_unsubscribeAndRecycle(): Subscriber<T> {
154-
const { _parent, _parents } = this;
155-
this._parent = null;
156-
this._parents = null;
154+
const { _parentOrParents } = this;
155+
this._parentOrParents = null;
157156
this.unsubscribe();
158157
this.closed = false;
159158
this.isStopped = false;
160-
this._parent = _parent;
161-
this._parents = _parents;
159+
this._parentOrParents = _parentOrParents;
162160
return this;
163161
}
164162
}

src/internal/Subscription.ts

+40-53
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ export class Subscription implements SubscriptionLike {
3030
public closed: boolean = false;
3131

3232
/** @internal */
33-
protected _parent: Subscription = null;
34-
/** @internal */
35-
protected _parents: Subscription[] = null;
33+
protected _parentOrParents: Subscription | Subscription[] = null;
3634
/** @internal */
3735
private _subscriptions: SubscriptionLike[] = null;
3836

@@ -53,55 +51,47 @@ export class Subscription implements SubscriptionLike {
5351
* @return {void}
5452
*/
5553
unsubscribe(): void {
56-
let hasErrors = false;
5754
let errors: any[];
5855

5956
if (this.closed) {
6057
return;
6158
}
6259

63-
let { _parent, _parents, _unsubscribe, _subscriptions } = (<any> this);
60+
let { _parentOrParents, _unsubscribe, _subscriptions } = (<any> this);
6461

6562
this.closed = true;
66-
this._parent = null;
67-
this._parents = null;
63+
this._parentOrParents = null;
6864
// null out _subscriptions first so any child subscriptions that attempt
6965
// to remove themselves from this subscription will noop
7066
this._subscriptions = null;
7167

72-
let index = -1;
73-
let len = _parents ? _parents.length : 0;
74-
75-
// if this._parent is null, then so is this._parents, and we
76-
// don't have to remove ourselves from any parent subscriptions.
77-
while (_parent) {
78-
_parent.remove(this);
79-
// if this._parents is null or index >= len,
80-
// then _parent is set to null, and the loop exits
81-
_parent = ++index < len && _parents[index] || null;
68+
if (_parentOrParents instanceof Subscription) {
69+
_parentOrParents.remove(this);
70+
} else if (_parentOrParents !== null) {
71+
for (let index = 0; index < _parentOrParents.length; ++index) {
72+
const parent = _parentOrParents[index];
73+
parent.remove(this);
74+
}
8275
}
8376

8477
if (isFunction(_unsubscribe)) {
8578
try {
8679
_unsubscribe.call(this);
8780
} catch (e) {
88-
hasErrors = true;
8981
errors = e instanceof UnsubscriptionError ? flattenUnsubscriptionErrors(e.errors) : [e];
9082
}
9183
}
9284

9385
if (isArray(_subscriptions)) {
94-
95-
index = -1;
96-
len = _subscriptions.length;
86+
let index = -1;
87+
let len = _subscriptions.length;
9788

9889
while (++index < len) {
9990
const sub = _subscriptions[index];
10091
if (isObject(sub)) {
10192
try {
10293
sub.unsubscribe();
10394
} catch (e) {
104-
hasErrors = true;
10595
errors = errors || [];
10696
if (e instanceof UnsubscriptionError) {
10797
errors = errors.concat(flattenUnsubscriptionErrors(e.errors));
@@ -113,7 +103,7 @@ export class Subscription implements SubscriptionLike {
113103
}
114104
}
115105

116-
if (hasErrors) {
106+
if (errors) {
117107
throw new UnsubscriptionError(errors);
118108
}
119109
}
@@ -164,14 +154,34 @@ export class Subscription implements SubscriptionLike {
164154
}
165155
}
166156

167-
if (subscription._addParent(this)) {
168-
// Optimize for the common case when adding the first subscription.
169-
const subscriptions = this._subscriptions;
170-
if (subscriptions) {
171-
subscriptions.push(subscription);
172-
} else {
173-
this._subscriptions = [subscription];
157+
// Add `this` as parent of `subscription` if that's not already the case.
158+
let { _parentOrParents } = subscription;
159+
if (_parentOrParents === null) {
160+
// If we don't have a parent, then set `subscription._parents` to
161+
// the `this`, which is the common case that we optimize for.
162+
subscription._parentOrParents = this;
163+
} else if (_parentOrParents instanceof Subscription) {
164+
if (_parentOrParents === this) {
165+
// The `subscription` already has `this` as a parent.
166+
return subscription;
174167
}
168+
// If there's already one parent, but not multiple, allocate an
169+
// Array to store the rest of the parent Subscriptions.
170+
subscription._parentOrParents = [_parentOrParents, this];
171+
} else if (_parentOrParents.indexOf(this) === -1) {
172+
// Only add `this` to the _parentOrParents list if it's not already there.
173+
_parentOrParents.push(this);
174+
} else {
175+
// The `subscription` already has `this` as a parent.
176+
return subscription;
177+
}
178+
179+
// Optimize for the common case when adding the first subscription.
180+
const subscriptions = this._subscriptions;
181+
if (subscriptions === null) {
182+
this._subscriptions = [subscription];
183+
} else {
184+
subscriptions.push(subscription);
175185
}
176186

177187
return subscription;
@@ -192,29 +202,6 @@ export class Subscription implements SubscriptionLike {
192202
}
193203
}
194204
}
195-
196-
/** @internal */
197-
private _addParent(parent: Subscription): boolean {
198-
let { _parent, _parents } = this;
199-
if (_parent === parent) {
200-
// If the new parent is the same as the current parent, then do nothing.
201-
return false;
202-
} else if (!_parent) {
203-
// If we don't have a parent, then set this._parent to the new parent.
204-
this._parent = parent;
205-
return true;
206-
} else if (!_parents) {
207-
// If there's already one parent, but not multiple, allocate an Array to
208-
// store the rest of the parent Subscriptions.
209-
this._parents = [parent];
210-
return true;
211-
} else if (_parents.indexOf(parent) === -1) {
212-
// Only add the new parent to the _parents list if it's not already there.
213-
_parents.push(parent);
214-
return true;
215-
}
216-
return false;
217-
}
218205
}
219206

220207
function flattenUnsubscriptionErrors(errors: any[]) {

0 commit comments

Comments
 (0)