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

mknichel/dedupe strings in module layers #18340

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

mknichel
Copy link

@mknichel mknichel commented Apr 19, 2024

What kind of change does this PR introduce?

This pull request adds a layer of caching for strings that could be duplicated in modules across multiple layers. This change was motivated by an observation that there were many strings in the heap duplicated in a Next.js application that uses App Router, which uses layers in the implementation. After this pull request, these strings were deduplicated saving the memory for any file that appears in multiple layers.

The cache here is associated with the compilation in a weak map so it can be garbage collected after the compilation finishes.

There should be no visible behavior difference after this pull request.

Did you add tests for your changes?

New tests were not added since the behavior should be covered by existing integration tests that use layers.

Does this PR introduce a breaking change?

No, this PR is not a breaking change. No user visible behavior is affected.

What needs to be documented once your changes are merged?

No changes would need to be documented after this pull request. This pull request only affects internal implementation details.

Copy link

linux-foundation-easycla bot commented Apr 19, 2024

CLA Not Signed

@webpack-bot
Copy link
Contributor

For maintainers only:

  • This needs to be documented (issue in webpack/webpack.js.org will be filed when merged)
  • This needs to be backported to webpack 4 (issue will be created when merged)

} else {
moduleLayerCache.set(module.userRequest, data);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we move it to method, to avoid duplicate logic (for example to the compilation class)

moduleLayerCache = new ModuleLayerCache();
cachedModuleLayerCaches.set(associatedObjectForCache, moduleLayerCache);
}
if (moduleLayerCache.has(this.userRequest)) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (moduleLayerCache.has(this.userRequest)) {
if (moduleLayerCache.has(this.request)) {

@@ -780,6 +784,31 @@ class NormalModule extends Module {
* @returns {Source} the created source
*/
createSource(context, content, sourceMap, associatedObjectForCache) {
// If this module exists in a layer, try to reuse the value for the
// source string so it is not duplicated in multiple modules.
if (this.layer && this.userRequest) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (this.layer && this.userRequest) {
if (this.layer && this.request) {

Copy link
Author

Choose a reason for hiding this comment

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

You need to use userRequest since at least in Next.js App Router, the values of request will be different for the same file across the different layers.

Copy link
Member

Choose a reason for hiding this comment

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

Could you use resource or resourcePath?

cachedModuleLayerCaches.set(associatedObjectForCache, moduleLayerCache);
}
if (moduleLayerCache.has(this.userRequest)) {
const cachedSource = moduleLayerCache.get(this.userRequest);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const cachedSource = moduleLayerCache.get(this.userRequest);
const cachedSource = moduleLayerCache.get(this.request);

Comment on lines +30 to +33
/** @type {Map<string, WeakMapKey>} */
this.weakMapKeys = new Map();
/** @type {WeakMap<WeakMapKey, string | Buffer>} */
this.cachedValues = new WeakMap();
Copy link
Member

Choose a reason for hiding this comment

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

This is not "weak". this.weakMapKeys holds a strong reference to the WeakMapKey, so it can't be collected while ModuleLayerCache is active. So the whole class is equal to a single Map<string, string | Buffer>

Copy link
Author

Choose a reason for hiding this comment

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

Is there another key that you could use? If so, just let me know. You need to use userRequest (a string) and therefore it has to be able to be a key in the WeakMap somehow. This holds on to the values during the lifetime of the Compilation object but then is thrown away.

Copy link
Member

Choose a reason for hiding this comment

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

I think you can't make it "weak" on the values. That's won't work with string keys. But I think that might not be needed. But you need to make it "weak" on associatedObjectForCache that should be enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants