Skip to content

Improves _.memoize performance #2461

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

Closed
wants to merge 3 commits into from
Closed

Conversation

caiogondim
Copy link

Creating an object without prototype proves to be faster than a literal
object, since we don't have to go through the prototype chain to check
if a key already exists.

With a memoized recursive fibonacci implementation, we could do on a
MacBook 13 Retina Late 2013 ~9.6M ops/sec, while with the improved
implementation we could get ~26M ops/sec. Almost a 3x improvement.

@caiogondim
Copy link
Author

Here the perf results:
screen shot 2016-03-07 at 21 07 26

Here the code with the perf benchmark:
https://gist.github.com/caiogondim/5e8334f7a70974e37590

var memoize = function(key) {
var cache = memoize.cache;
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
if (typeof cache[address] === 'undefined') cache[address] = func.apply(this, arguments);
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't this need to be forked a bit more for enviros without Object.create which still need the own property checks.

@akre54
Copy link
Collaborator

akre54 commented Mar 7, 2016

This is the kind of thing a generic _.Setwould be good for.

@jdalton
Copy link
Contributor

jdalton commented Mar 7, 2016

This is the kind of thing a generic _.Set would be good for.

Yes! \o/

Lodash does this with _.memoize.Cache which is a map-like implementation MapCache (uses ES6 Map when available under the hood). It can be swapped out with WeakMap or Immutable.Map. Internally there's also a Hash, Stack and SetCache.

Related to #1862.

@akre54
Copy link
Collaborator

akre54 commented Mar 7, 2016

Sure, perhaps it's time to add it.

@caiogondim
Copy link
Author

I totally agree a generic Set would be better. But that comes with a bigger refactor.

@jdalton Already updated the code to properly check for keys in objects with and without prototype.

@caiogondim
Copy link
Author

With my last change, still getting something around 3x more performance.

if (cache.constructor === Object) {
return _.has(cache, address);
} else {
return (typeof cache[address] !== 'undefined');
Copy link
Collaborator

Choose a reason for hiding this comment

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

The issue with this is undefined is a valid return value that should be memoized. We'll need to use _.has in every case.

Copy link
Author

Choose a reason for hiding this comment

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

Makes sense!
Should I add a test for this case?
Right now everything is passing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please. 😄

@caiogondim
Copy link
Author

Just added the test case to return undefined.
It was indeed not being memoized.

@caiogondim
Copy link
Author

Did a rebase as well.
Should be good to go.

var memoize = function(key) {
var cache = memoize.cache;
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
if (!(_.has(cache, address))) cache[address] = func.apply(this, arguments);
return cache[address];
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you still seeing a perf win?
If you aren't don't close the PR. I'll walk you through some things to improve it.

Copy link
Author

Choose a reason for hiding this comment

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

No perf win using _.has =/
Can you spot something we could improve over here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hey @caiogondim!

I created a screencast to dig into MapCache a bit more.
You can follow along at lodash._mapcache/index.js.

Update:
Note: The Hash and HASH_UNDEFINED bits are something I learned from Ember.

Caio Gondim added 3 commits March 9, 2016 21:09
Creating an object without prototype proves to be faster than a literal
object, since we don't have to go through the prototype chain to check
if a key already exists.

With a memoized recursive fibonacci implementation, we could do on a
MacBook 13 Retina Late 2013 ~9.6M ops/sec, while with the improved
implementation we could get ~26M ops/sec. Almost a 3x improvement.
Now checking properly the existence of keys in objects with and without
prototype.
@caiogondim
Copy link
Author

Just resolved the conflict.

@jgonggrijp
Copy link
Collaborator

Closing this as it seems the performance gain has been lost in the process of making this work correctly, and as a performance enhancement based on #1862 would seem preferable.

@jgonggrijp jgonggrijp closed this May 9, 2020
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