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

module: add cache of request and filename to optimize require perf #21404

Closed
wants to merge 1 commit into from
Closed

module: add cache of request and filename to optimize require perf #21404

wants to merge 1 commit into from

Conversation

ghost
Copy link

@ghost ghost commented Jun 19, 2018

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@ghost ghost mentioned this pull request Jun 19, 2018
4 tasks
return filename;
};
Module._resolveFilename.cache = new Map();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we attaching this to a function?

Choose a reason for hiding this comment

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

Thanks for your review. Here is the context: #21300
Make a internal cache for request and filename to avoid the additional performance cost

Copy link
Contributor

@mscdex mscdex Jun 19, 2018

Choose a reason for hiding this comment

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

What I meant was why attach an object to a function value vs. say adding Module._filenameCache and using that instead? I also get that this is how the stat() function is currently working, but I am not sure how adding properties to function values affects performance of that function or the property being added as far as V8 is concerned.

At the very least, I think it would be a good idea to test both methods and see if there is any change in performance.

Copy link
Member

Choose a reason for hiding this comment

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

I am not sure how adding properties to function values affects performance of that function or the property being added as far as V8 is concerned

It should make no difference.

if (!filename) {
// eslint-disable-next-line no-restricted-syntax
var err = new Error(`Cannot find module '${request}'`);
err.code = 'MODULE_NOT_FOUND';
throw err;
}
if (parentFilename) {
const tempObj = cache.get(parentFilename) || {};
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps Object.create(null) should be used instead of {}.

Choose a reason for hiding this comment

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

Thanks, I'll modify this

return filename;
};
Module._resolveFilename.cache = new Map();
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, is using Map instead of a plain object (e.g. Object.create(null)) actually faster?

Choose a reason for hiding this comment

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

if (depth === 0) stat.cache = new Map();

I saw that stat.cache inside this file used Map for the cache container, based on what is considered?

// 3. don't have cache for this parent.filename
if (!(options && options.paths) &&
!parentFilename &&
cache.get(parentFilename) !== undefined) {
Copy link
Contributor

@mscdex mscdex Jun 19, 2018

Choose a reason for hiding this comment

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

Doing the lookup twice is wasteful. I'd lookup and store the value the first time and then check if that stored value is undefined and use it again below.

Choose a reason for hiding this comment

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

Thanks, I'll modify this.

@mapleeit
Copy link

Hi @bnoordhuis , I re-think about making a global cache instead of a cache in the Module constructor. There may have some side effects when delete require.cache[filename]. The cache will not be deleted as expected.

if (!filename) {
// eslint-disable-next-line no-restricted-syntax
var err = new Error(`Cannot find module '${request}'`);
err.code = 'MODULE_NOT_FOUND';
throw err;
}
if (parentFilename) {
const tempObj = cache.get(parentFilename) || {};
Copy link
Contributor

Choose a reason for hiding this comment

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

tempObj is not a good name for variable, maybe filenameCache ?

// 2. don't have parent.filename
// 3. don't have cache for this parent.filename
if (!(options && options.paths) &&
!parentFilename &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Umm... Should this line change into parentFilename && ... ? It should be truthy when using the cache.

// do not use the cache
// 1. require.resolve('you/request', {paths: []})
// 2. don't have parent.filename
// 3. don't have cache for this parent.filename
Copy link
Contributor

Choose a reason for hiding this comment

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

Comments here is a little bit confusing. It's better to say "Using the cache when ... "

@starkwang
Copy link
Contributor

It's better to add a benchmark for this change. You can refer to the guide for writing and running benchmarks.

@mapleeit
Copy link

@starkwang Thanks for your review and advice! I'll append benchmark later.

But here is still a problem to discuss. Using a global cache or using a cache in the parent module. @bnoordhuis advised that use a global cache like the existing stat.cache. But this will not be deleted when people use delete require.cache[filename]. I'm a little concerned about that a global cache may cause some side effects when deleting cache.

Do you have any advise for this?

@ghost
Copy link
Author

ghost commented Jul 11, 2018

But here is still a problem to discuss. Using a global cache or using a cache in the parent module. @bnoordhuis advised that use a global cache like the existing stat.cache. But this will not be deleted when people use delete require.cache[filename]. I'm a little concerned about that a global cache may cause some side effects when deleting cache.

ping @bnoordhuis

@jasnell
Copy link
Member

jasnell commented Sep 10, 2018

What's the status on this one?

@jasnell jasnell added the stalled Issues and PRs that are stalled. label Sep 10, 2018
@mapleeit
Copy link

mapleeit commented Feb 22, 2019

Added benchmark.

benchmark result:

image

if (filename) return filename;
}
}

Copy link
Member

@jdalton jdalton Feb 22, 2019

Choose a reason for hiding this comment

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

Instead of caching per parent would it be more beneficial to cache from the path.dirname(parent.filename). Something like:

cacheKey =
  request + "\0" +
  fromPath + "\0" +
  (isMain ? "1" : "")

Choose a reason for hiding this comment

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

Do you mean that using a global cache, which uses request + parentDirName + isMain as keys?
But this will not be deleted when people use delete require.cache[filename]. I'm a little concerned about that a global cache may cause some side effects when deleting cache.

@fhinkel
Copy link
Member

fhinkel commented Oct 26, 2019

Ping @mapleeit

@mapleeit
Copy link

@fhinkel yeah, I'm here. Sorry for the suspending. I'll review this one again this weekend.

@guybedford
Copy link
Contributor

A lot of other caching improvements have landed since this PR was made (#26970 is exactly a parent resolution cache like this PR). Perhaps that removes the need for this PR now?

@mapleeit
Copy link

mapleeit commented Nov 1, 2019

@guybedford Thanks for pointing it out. Glad to know there is already a merged PR. I'm closing this one.

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stalled Issues and PRs that are stalled.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants