webpack-sources icon indicating copy to clipboard operation
webpack-sources copied to clipboard

[Memory] Add options to reduce memory consumed by RawSource

Open mknichel opened this issue 9 months ago • 2 comments

This PR adds two options to reduce memory used by RawSource: disable dual caching of string and buffer content and intern strings across source objects. These issues were observed while investigating memory usage of Next.js applications where Webpack had duplicate copies of strings/buffers in memory.

These options are optional so that they should not affect existing users but can be enabled for users who wish to reduce memory usage at the potential of a slight compilation time increase.

Disable dual caching of string and buffer contents

Upon creation, RawSource stores either a string or buffer. When requested, it will create the other type and then store it in memory for any future calls:

		if (this._valueAsBuffer === undefined) {
			this._valueAsBuffer = Buffer.from(this._value, "utf-8");
		}
		return this._valueAsBuffer;

This will cause the same content to be stored twice in memory, roughly doubling the memory consumption for RawSource objects (a buffer will be slightly bigger than the string, but not by too much).

This will save any future conversion that has to be done. However, these conversions aren't too expensive CPU wise (Buffer.from runs at 34000-36000 ops/sec on an Apple M1 Pro on a 150kb file), so it makes sense for large applications or memory constrained applications to disable this with only slight increases in compile times.

Intern strings across Raw Sources

Files that are repeated in multiple Webpack layers will be loaded into multiple RawSource objects and repeat the file contents in memory. Next.js App Router uses Webpack layers and therefore we are seeing file contents in next build appear duplicate times.

An option to intern strings is added in this pull request. With string interning, the same reference will be used across multiple RawSources so the memory isn't duplicated. v8 doesn't support string interning for dynamically allocated strings, but this can be implemented in JavaScript with a Map. The caller is responsible for deleting the contents of the string intern cache when it is no longer needed (such as at the end of a compilation).

Impact

I tested next build for 3 different Next.js applications with these changes: nodejs.org, and two private Next.js applications of medium and large sizes.

  • Nodejs.org would save ~20 MB of heap by enabling these optimizations out of ~315MB total used by the compilation and increase compile times by < 1 second
  • Internal medium sized Next.js application would save ~270MB of memory with these optimizations out of ~2.1GB heap total and would increase compile times by ~1-2 seconds.
  • Internal large Next.js application would save ~330MB of memory with these optimizations and would increase compile times by ~10 seconds.

mknichel avatar May 20 '24 23:05 mknichel