Increase default 'max_semi_space_size' value to reduce GC overhead in V8 #42511
Description
What is the problem this feature will solve?
When I use node to run the web-tooling-benchmark, I found that the runtime flag --max_semi_space_size
have a big impact on the test result. The total throughput increased about 18% after I pass the runtime flag --max_semi_space_size=128
into node. So I did some investigate about the 'max_semi_space_size' flag.
From some v8 official blogs (Getting garbage collection for free, orinoco-parallel-scavenger), I found there are two garbage collection strategies in V8:
Scavenge GC: fast, high frequency, for young generation objects.
Major GC: take a long time, low frequency, full garbage collections.
When we create a new object by javascript code, the object will be put into semi_space as a young generation object. And when the semi_space is about to use up, V8 engine will trigger the Scavenge GC to clean up the garbage objects in semi_space.
If I use the --max_semi_space_size
flag to increase the maximum limit of semi_space size, the scavenge GC occur frequency will decrease. This will bring both advantages and disadvantages:
- Advantage: throughput improvement. Because the scavenge GC occur frequency decreased, the total GC pause time will also decrease, then node can have more CPU resource to execute the javascript code.
- Disadvantage: more memory usage. This is obviously, more semi_space size will cost more memory.
It's a trade-off between time and space. V8 set the default max_semi_space_size
as 16MB for 64bit system and 8MB for 32bit system (related code). I think it's a heuristic value that mainly considered client device with small RAM size (for example: some android device only have 4GB RMA). But for server scenarios, memory usually isn't the bottleneck, while throughput is the actual bottleneck.
So the problem is:
Whether the currently default max_semi_space_size
(16MB/8MB) for V8 is also the best configuration for node?
What is the feature you are proposing to solve the problem?
To solve the problem above, I tuned the --max_semi_space_size
(16MB, 32MB, 64MB, 128MB, 256MB) and tested on web-tooling-benchmark and a simple service based on ghost.js. Here is the test results:
From the figure we can see that:
- Peak memory usage increases linearly with max_semi_space_size.
- Throughput grows fast when max_semi_space_size less than 128MB, then keep flat when max_space_size bigger than 128MB.
- The scale of the throughput improvement is workload-dependent, probably due to the greater GC pressure from web-tooling-benchmark.
So I think we can choose a better max_semi_space_size
value and pass this runtime flag to V8 when node startup.
What alternatives have you considered?
Test environment:
- Hardware:
- CPU: Intel(R) Xeon(R) Platinum 8358 CPU @ 2.60GHz
- RAM: 500GB
- Software:
- OS: Ubuntu 20.04.1 (x86_64)
- Linux version: 5.11.0-41-generic
- Docker version: 20.10.11
- node version: v18.0.0-pre
Test process:
- Build the docker container with node binary and workload in it.
- Start multi-containers (containers number equals vCPU number) to make sure the system's total CPU usage rate >90%.
- Running the workload in started containers concurrently and monitor the system's total memory usage periodically.
Activity