Skip to content
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

Simplifies shouldComponentUpdate (open discussion) #78

Merged
merged 8 commits into from
Mar 5, 2015

Conversation

mikaelbr
Copy link
Member

Simplifications to shouldComponentUpdate now as we have _.isEqual.

Started implementation based on some comments from #76 - and this is a continuation of the discussion. Not yet implemented "rebase" support. As that may be a separate change.

Nor is comparable implemented. As there are some discussion if we need to do deep equal on immutable structures that has been said to not be equal through reference checks.

@mikaelbr mikaelbr force-pushed the shouldComponentUpdateSimplified branch from 17a7db2 to 446ed40 Compare February 28, 2015 18:23
@mikaelbr mikaelbr changed the title Simplifies shouldComponentUpdate and switch from isImmutable to isComparable Simplifies shouldComponentUpdate (open discussion) Feb 28, 2015
if (currentCursorsKeys.length !== nextCursorsKeys.length) {
if (debug) debug.call(this, 'shouldComponentUpdate => true (number of cursors differ)');
if (nextProps !== this.props) {
if (debug) debug.call(this, 'shouldComponentUpdate => true (equal input)');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*unequal? 😄

@mikaelbr mikaelbr force-pushed the shouldComponentUpdateSimplified branch from 1d36b75 to d9541ef Compare February 28, 2015 18:34
@torgeir
Copy link
Member

torgeir commented Feb 28, 2015

Faster/easier should component update 👍 !

Though, I think for something like this we need some hard numbers on comparison speeds before and after these changes.

@mikaelbr
Copy link
Member Author

I'm working on a benchmark. Copied Immutable.js' benchmark and trying to run it now. It compares previous dist file with current uncommitted dist.

var isNextImmutable = _isImmutable(next);

if (isCurrentImmutable && isNextImmutable) {
return current.equals(next);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I kind of like the idea of an interface for saying you want component args to trigger renders or not when changed, I'm still not conviced Immutable.js .equals() is the way to go.

@mikaelbr mikaelbr force-pushed the shouldComponentUpdateSimplified branch from 7972811 to fec0172 Compare February 28, 2015 18:43
if (debug) debug.call(this, 'shouldComponentUpdate => true (cursors have changed)');
return true;
}
var isNotIgnorable = not(or(isStatics, isChildren));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this outside of shouldComponentUpdate for caching. No need to reconstruct this higher-order function on every render.

@dashed
Copy link
Contributor

dashed commented Feb 28, 2015

👍 Awesome! shouldComponentUpdate looks so much easier to read, extend, and refactor.

@mikaelbr Looking forward to see the benchmarks.

I wonder if isEqualProps and isEqualState can be made more DRY.


@Gozala Do you agree that there's a compromise where one can override isEqualState and/or isEqualProps for a more aggressive shouldComponentUpdate?

}
var isNotIgnorable = not(or(isStatics, isChildren));
var nextProps = filter(nextProps, isNotIgnorable),
currentProps = filter(this.props, isNotIgnorable);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this below _isEqualState block for better placement:

    var nextProps    = filter(nextProps, isNotIgnorable),
        currentProps = filter(this.props, isNotIgnorable);

Reasoning is that it could be a pointless computation if !_isEqualState(this.state, nextState) === true.

@mikaelbr
Copy link
Member Author

Thanks for good feedback.

I haven't gotten the benchmark to work as I want yet, and haven't had the time to work on it too much, but it'll get there.

@@ -236,6 +184,22 @@ function factory (methods) {
}
}

function compare (current, next, comparator, equalCheck) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nitpick. I think comparator is the wrong name.

Maybe rename to typecheck or similar.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a hard time choosing a name. I agree. Fixing

@dashed
Copy link
Contributor

dashed commented Feb 28, 2015

New changes looks good to me. 😄

if (hasChangedCursors(currentCursors, nextCursors)) {
if (debug) debug.call(this, 'shouldComponentUpdate => true (cursors have changed)');
return true;
if (nextProps === this.props) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if assert(nextProps === this.props && nextState !== this.state)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right.

@mikaelbr
Copy link
Member Author

mikaelbr commented Mar 1, 2015

Here's the initial relative benchmarks for the current dist and this dist:

shouldComponentUpdate on no input
  Old:   180,556   182,225   183,925 ops/sec
  New:   255,906   258,463   261,071 ops/sec
  compare: 1 1
  diff: 41.83%
  rme: 0.96%
shouldComponentUpdate when immutables are same
  Old:    71,202    71,945    72,703 ops/sec
  New:   107,211   108,415   109,646 ops/sec
  compare: 1 1
  diff: 50.69%
  rme: 1.08%
shouldComponentUpdate when many immutables are passed as the same reference
  Old:    10,573    10,878    11,201 ops/sec
  New:    11,915    12,060    12,208 ops/sec
  compare: 1 1
  diff: 10.86%
  rme: 2.21%
shouldComponentUpdate when large object is passed
  Old:    20,329    20,540    20,754 ops/sec
  New:    22,322    22,502    22,685 ops/sec
  compare: 1 1
  diff: 9.55%
  rme: 0.92%
shouldComponentUpdate when two large object is passed
  Old:     1,265     1,280     1,296 ops/sec
  New:     1,523     1,537     1,552 ops/sec
  compare: 1 1
  diff: 20.07%
  rme: 1.06%
shouldComponentUpdate when two large object is passed but as same input
  Old:     2,381     2,424     2,469 ops/sec
  New:    29,189    29,421    29,656 ops/sec
  compare: 1 1
  diff: 1113.38%
  rme: 1.39%
shouldComponentUpdate when immutables are different
  Old:    40,521    42,657    45,029 ops/sec
  New:    42,122    45,864    50,336 ops/sec
  compare: 1 1
  diff: 7.51%
  rme: 7.3%
shouldComponentUpdate when long arrays (1000 elements) are the same
  Old:     1,587     1,623     1,660 ops/sec
  New:     8,076     8,466     8,895 ops/sec
  compare: 1 1
  diff: 421.57%
  rme: 3.76%
shouldComponentUpdate when long arrays (1000 elements) are the same reference
  Old:    13,032    13,229    13,432 ops/sec
  New:    13,451    13,720    14,000 ops/sec
  compare: 1 1
  diff: 3.71%
  rme: 1.77%
shouldComponentUpdate when same input is passed (long array, 1000 items)
  Old:     6,381     6,461     6,543 ops/sec
  New:     8,359     9,117    10,027 ops/sec
  compare: 1 1
  diff: 41.1%
  rme: 6.47%
all done

Haven't looked too much into this, so these numbers might be wrong, but they seem reasonable. I didn't get cursors to work, but immutable structures should works just as good.

The tests can be found here: https://gist.github.com/mikaelbr/b4760464013fe636186f
Again, I haven't looked too much into this and mostly just copy/pasted and tested it out - so there's very likely that there are some dumb tests.

@dashed
Copy link
Contributor

dashed commented Mar 1, 2015

Here are my results with iojs-v1.4.1.

shouldComponentUpdate on no input
  ...running...  
  Old:   134,954   137,313   139,756 ops/sec
  New:   131,645   136,062   140,785 ops/sec
  compare: 1 1
  diff: -0.92%
  rme: 2.67%
shouldComponentUpdate when immutables are same
  ...running...  
  Old:    46,624    51,573    57,696 ops/sec
  New:    55,795    65,344    78,838 ops/sec
  compare: 1 1
  diff: 26.7%
  rme: 14.24%
shouldComponentUpdate when many immutables are passed as the same reference
  ...running...  
  Old:     7,545     7,653     7,764 ops/sec
  New:     8,099     8,217     8,339 ops/sec
  compare: 1 1
  diff: 7.37%
  rme: 1.44%
shouldComponentUpdate when large object is passed
  ...running...  
  Old:    10,774    10,864    10,956 ops/sec
  New:    11,476    11,635    11,800 ops/sec
  compare: 1 1
  diff: 7.09%
  rme: 1.14%
shouldComponentUpdate when two large object is passed
  ...running...  
  Old:       649       662       676 ops/sec
  New:       838       852       867 ops/sec
  compare: 1 1
  diff: 28.71%
  rme: 1.89%
shouldComponentUpdate when two large object is passed but as same input
  ...running...  
  Old:     1,134     1,149     1,165 ops/sec
  New:    14,020    14,176    14,334 ops/sec
  compare: 1 1
  diff: 1132.72%
  rme: 1.22%
shouldComponentUpdate when immutables are different
  ...running...  
  Old:    27,957    28,465    28,991 ops/sec
  New:    40,528    41,205    41,906 ops/sec
  compare: 1 1
  diff: 44.75%
  rme: 1.74%
shouldComponentUpdate when long arrays (1000 elements) are the same
  ...running...  
  Old:       585       593       602 ops/sec
  New:     2,243     2,285     2,329 ops/sec
  compare: 1 1
  diff: 285.03%
  rme: 1.65%
shouldComponentUpdate when long arrays (1000 elements) are the same reference
  ...running...  
  Old:     4,664     4,749     4,837 ops/sec
  New:     4,758     4,849     4,943 ops/sec
  compare: 1 1
  diff: 2.11%
  rme: 1.86%
shouldComponentUpdate when same input is passed (long array, 1000 items)
  ...running...  
  Old:     4,529     4,628     4,732 ops/sec
  New:     5,084     5,179     5,278 ops/sec
  compare: 1 1
  diff: 11.89%
  rme: 2.04%
all done

@dashed
Copy link
Contributor

dashed commented Mar 1, 2015

@dashed
Copy link
Contributor

dashed commented Mar 1, 2015

The benchmark was flawed. Immutable structures weren't actually created at all!

Here is why: /~https://github.com/Dashed/omniscient/blob/c31930e70023d5d524d23ffc48919c8460cc97b3/perf/tests.js#L5-L11

I augmented the benchmarks with two working cursor benchmarks:
dashed@c31930e

My results: /~https://github.com/Dashed/omniscient/blob/c31930e70023d5d524d23ffc48919c8460cc97b3/bench/results.txt

@mikaelbr Feel free to add more cursor tests.

@torgeir
Copy link
Member

torgeir commented Mar 1, 2015

a reasonable assumption would be omniscient folks use more props than state - should we move the props check before the state check?

@mikaelbr
Copy link
Member Author

mikaelbr commented Mar 1, 2015

I made the same assumption, but a different conclusion. As state is rarely used, it's most likely smaller than props and thus if they differ the check will be cheaper. Also, if it is empty (as it is 98% of the time) the check is also cheap as you don't have to do a lot of iterations. Might add som benchmarks to see the actual numbers - when we get confident benchmarks.

@dashed
Copy link
Contributor

dashed commented Mar 1, 2015

Maybe we can make the component smarter to adjust priority in checks based on previous re-renders?

@dashed
Copy link
Contributor

dashed commented Mar 1, 2015

Tradeoff is that a new shouldComponentUpdate instance would be instantiated on every component call.

@dashed
Copy link
Contributor

dashed commented Mar 2, 2015

We can utilize this to decide whether to run _isEqualProps or _isEqualState (since either are expensive computations).

http://www.loper-os.org/bad-at-entropy/manmach.html
https://www.reddit.com/comments/1p3ti1

We can modify this so that when the Machine passes, we use random chance to execute _isEqualProps and _isEqualState in any order.

It isn't a heavyweight predictive modeler, but decent enough 😄

@dashed
Copy link
Contributor

dashed commented Mar 2, 2015

This would be a consideration for a separate PR once this PR simmers.

@mikaelbr
Copy link
Member Author

mikaelbr commented Mar 5, 2015

I'm merging this now as a simple code optmimization/reduction. We're thinking about the .equals approach some more, so that would be a separate implementation anyways. And make #82 and #85 redundant.

mikaelbr added a commit that referenced this pull request Mar 5, 2015
Simplifies shouldComponentUpdate (open discussion)
@mikaelbr mikaelbr merged commit 9f3715c into master Mar 5, 2015
@facundocabrera
Copy link

@mikaelbr, @dashed Can I ask how are you running the benchmarks? I want to analyze my current implementation using omniscient with the same approach (I'm switching from v2 to v3).

Thanks!

@mikaelbr
Copy link
Member Author

Used a variation of immutable.js benchmarks: https://gist.github.com/mikaelbr/c7e37f5865c494a6e0fa

@Gozala
Copy link
Contributor

Gozala commented Apr 20, 2015

@Gozala Do you agree that there's a compromise where one can override isEqualState and/or isEqualProps for a more aggressive shouldComponentUpdate?

I am not sure what the context of this question is. In principal having a control over each one sounds right though. As of my personal take I don't use omniscient for components with a local state, those in my code are usually shims that I use react API directly.

@mikaelbr mikaelbr deleted the shouldComponentUpdateSimplified branch September 26, 2015 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants