-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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
Conversation
lib/internal/modules/cjs/loader.js
Outdated
return filename; | ||
}; | ||
Module._resolveFilename.cache = new Map(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
lib/internal/modules/cjs/loader.js
Outdated
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) || {}; |
There was a problem hiding this comment.
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 {}
.
There was a problem hiding this comment.
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
lib/internal/modules/cjs/loader.js
Outdated
return filename; | ||
}; | ||
Module._resolveFilename.cache = new Map(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
lib/internal/modules/cjs/loader.js
Outdated
// 3. don't have cache for this parent.filename | ||
if (!(options && options.paths) && | ||
!parentFilename && | ||
cache.get(parentFilename) !== undefined) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Hi @bnoordhuis , I re-think about making a global cache instead of a cache in the |
lib/internal/modules/cjs/loader.js
Outdated
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) || {}; |
There was a problem hiding this comment.
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
?
lib/internal/modules/cjs/loader.js
Outdated
// 2. don't have parent.filename | ||
// 3. don't have cache for this parent.filename | ||
if (!(options && options.paths) && | ||
!parentFilename && |
There was a problem hiding this comment.
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.
lib/internal/modules/cjs/loader.js
Outdated
// 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 |
There was a problem hiding this comment.
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 ... "
It's better to add a benchmark for this change. You can refer to the guide for writing and running benchmarks. |
@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 Do you have any advise for this? |
ping @bnoordhuis |
What's the status on this one? |
if (filename) return filename; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
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" : "")
There was a problem hiding this comment.
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.
Ping @mapleeit |
@fhinkel yeah, I'm here. Sorry for the suspending. I'll review this one again this weekend. |
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? |
@guybedford Thanks for pointing it out. Glad to know there is already a merged PR. I'm closing this one. |
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes