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

add short circuit for patching memoized vnodes #378

Merged

Conversation

andyrj
Copy link
Contributor

@andyrj andyrj commented Sep 20, 2017

this adds a short circuit to patch for memoized components...

example memoize:

/*
* memoize.js
* by @philogb and @addyosmani
* with further optimizations by @mathias
* and @DmitryBaranovsk
* perf tests: http://bit.ly/q3zpG3
* Released under an MIT license.
*/
function memoize( fn ) {
    return function () {
        var args = Array.prototype.slice.call(arguments),
            hash = "",
            i = args.length;
        currentArg = null;
        while (i--) {
            currentArg = args[i];
            hash += (currentArg === Object(currentArg)) ?
            JSON.stringify(currentArg) : currentArg;
            fn.memoize || (fn.memoize = {});
        }
        return (hash in fn.memoize) ? fn.memoize[hash] :
        fn.memoize[hash] = fn.apply(this, args);
    };
}

Users would wrap their components/view with this method to skip patching vnodes which have not changed.

This PR satisifies the following issue: #373

@codecov
Copy link

codecov bot commented Sep 20, 2017

Codecov Report

Merging #378 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@          Coverage Diff          @@
##           master   #378   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           2      2           
  Lines         148    150    +2     
  Branches       46     47    +1     
=====================================
+ Hits          148    150    +2
Impacted Files Coverage Δ
src/app.js 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update df84aba...4f56858. Read the comment docs.

Copy link
Owner

@jorgebucaran jorgebucaran left a comment

Choose a reason for hiding this comment

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

Needs tests and a demo to address #373.

😉🙏

@jorgebucaran jorgebucaran added the enhancement New feature or request label Sep 20, 2017
@okwolf okwolf mentioned this pull request Sep 20, 2017
7 tasks
src/app.js Outdated
@@ -180,6 +180,10 @@ export function app(props) {
}

function patch(parent, element, oldNode, node, isSVG, nextSibling) {
if (oldNode === node) {
console.log("memoized...")
Copy link
Contributor

Choose a reason for hiding this comment

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

I love console.log as much as the next guy, but perhaps it's not needed here?

Copy link
Contributor Author

@andyrj andyrj Sep 20, 2017

Choose a reason for hiding this comment

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

was just to help me verify that it was actually running that memoize case, trying to rack down the uncovered line 265 in coverage report LOL

test/app.test.js Outdated
};
}

test("skips patching memoized vnodes", done => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this actually check that the same VNode is returned?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

trying to figure out how to write the test for that...

Copy link
Owner

Choose a reason for hiding this comment

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

I think another approach to this would be to create a simple demo app with one memoized component and one component not. Then somehow benchmark it, and once we understand the problem well, take another shot at this?

Copy link
Contributor

Choose a reason for hiding this comment

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

You can call the memoized function twice and check for identity equality.

@andyrj
Copy link
Contributor Author

andyrj commented Sep 20, 2017

if anyone can help me see why line 265 is not covered after adding the un-connected conditional at the top of patch, it would be appreciated... gonna have another look in the morning after some coffee.

src/app.js Outdated
@@ -181,7 +181,6 @@ export function app(props) {

function patch(parent, element, oldNode, node, isSVG, nextSibling) {
if (oldNode === node) {
console.log("memoized...")
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@andyrj
Copy link
Contributor Author

andyrj commented Oct 3, 2017

ok so I have screenshots of chrome devtools performance tab with non-memoized, memoized without patch shortcut, and memoized with patch shortcut.

Non-memoized:
no-memo-table-no-shortcut

Memoized-no-shortcut:
memoized-table-perf-no-shortcut

Memoized-with-shortcut:
table-memo-with-shortcut

You can see the JS (yellow) in the cpu time decreasing between the screenshots with each part of the optimization enabled...

This was made via this codepen, https://codepen.io/andyrj/pen/yzzmLo, which is updating state every 16ms to increment a counter with a table that is based on static data below it... If this codepen is not sufficient to demonstrate the js overhead associated with a component that would benefit from memoization I don't really know what's going to satisfy a demo outside of a real app with expensive components.

@pspeter3
Copy link
Contributor

pspeter3 commented Oct 8, 2017

What is required to make this approved?

@jorgebucaran
Copy link
Owner

@pspeter3 Just a little time. 🙏

@andyrj
Copy link
Contributor Author

andyrj commented Oct 8, 2017

There was a branch missing in coverage last I looked, and I still don't like the test I added... Just couldn't think of a better one at the time.

@codecov-io
Copy link

codecov-io commented Nov 4, 2017

Codecov Report

Merging #378 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@          Coverage Diff          @@
##           master   #378   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           1      1           
  Lines         144    145    +1     
  Branches       42     43    +1     
=====================================
+ Hits          144    145    +1
Impacted Files Coverage Δ
src/index.js 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7319f27...d3c0b76. Read the comment docs.

@lukejacksonn
Copy link
Contributor

I was just going through issues and came from #373 (comment) to here.. when I first read this PR description (and graphs 💯 ) I thought wow this is awesome, but assumed for some reason that we would need to include the memoize function in core.. but this is not the case, right? Just an equality check will suffice. Which will always return false unless you have wrapped your component with memoize in userland. If I am understanding this correctly then what have we got against the idea?

src/app.js Outdated
@@ -196,7 +196,9 @@ export function app(props, container) {
}

function patchElement(parent, element, oldNode, node, isSVG, nextSibling) {
if (oldNode == null) {
if (oldNode === node) {
Copy link
Owner

@jorgebucaran jorgebucaran Nov 5, 2017

Choose a reason for hiding this comment

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

@andyrj If this is all we need, we can simplify it as follows. (bytes)

if (oldNode===node){
} else if (oldNode == null) {
...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call I wasn't taking the return at bottom into account for bytes.

Copy link
Owner

Choose a reason for hiding this comment

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

Okay, please rebase. 🙏

@andyrj
Copy link
Contributor Author

andyrj commented Nov 5, 2017

@lukejacksonn ya the memoize doesn't need to be a part of core. I just copy pasted that memoize() in comments above as an example someone could use.

@lukejacksonn
Copy link
Contributor

@andyrj 👌 and although I appreciate not every app/developer will take advantage of this feature nor will you need to memoize most of the time.. it does make sense to enable memoizing for when components get really big (more precisely, when the cost of diffing args and cacheing becomes cheaper than a straight up re-render, right?). If this is all that it takes then I'd say it is worth the minimal extra bytes and added complexity!

@jorgebucaran jorgebucaran merged commit f415e8e into jorgebucaran:master Nov 5, 2017
@jorgebucaran
Copy link
Owner

Thanks @andyrj! 🎉 🌮 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants