perf(@angular/build): process batch locales sequentially in i18n inliner worker - #34211
Conversation
…ner worker Executing batch locales concurrently via Promise.all within a single worker thread provided no concurrency benefit because inlining operations (MagicString transformations and source map decoding/remapping) are predominantly synchronous and CPU-bound on Node's single-threaded event loop. Holding concurrent MagicString instances, intermediate AST slices, and decoded source map mappings simultaneously inflated peak worker heap and RSS (especially for smaller chunks where up to 8 locales are processed in a single batch). Switching to sequential iteration allows intermediate transformation and mapping structures to be garbage-collected between locales, reducing peak RSS and slightly improving throughput due to reduced GC pressure.
… files in i18n inliner Dominant files (such as main.js or other large bundles >= 70% of max file size) previously sharded their locales across all available workers in the pool. On high-core machines, this dispatched heavy AST parsing and sourcemap remapping simultaneously across all worker threads, monopolizing the pool, causing memory bus/cache contention, and spiking peak V8 heap and RSS. A MAX_DOMINANT_WORKERS constant of 4 now caps the number of worker threads concurrently processing a single dominant file. Additionally, when multiple files exist in the window, workers are dynamically allocated to guarantee that at least one worker thread is always available to process smaller chunks concurrently without starvation. A new multi-dominant scenario has also been added to the benchmark suite to measure throughput and memory under multi-bundle workloads.
There was a problem hiding this comment.
Code Review
This pull request optimizes memory usage and concurrency management during i18n inlining. In i18n-inliner-worker.ts, parallel processing of locales via Promise.all is replaced with a sequential for...of loop to reduce peak memory. In i18n-inliner.ts, a concurrency cap (MAX_DOMINANT_WORKERS = 4) is introduced for dominant files to prevent V8 heap and RSS explosion on high-core machines, while also ensuring at least one worker remains available for smaller chunks to avoid starvation. Additionally, a new benchmark scenario (multi-dominant) is added to test these changes under contention. There are no review comments, so no further feedback is provided.
|
This PR was merged into the repository. The changes were merged into the following branches:
|
Executing batch locales concurrently via Promise.all within a single
worker thread provided no concurrency benefit because inlining operations
(MagicString transformations and source map decoding/remapping) are
predominantly synchronous and CPU-bound on Node's single-threaded event loop.
Holding concurrent MagicString instances, intermediate AST slices, and
decoded source map mappings simultaneously inflated peak worker heap and RSS
(especially for smaller chunks where up to 8 locales are processed in a single
batch).
Switching to sequential iteration allows intermediate transformation and
mapping structures to be garbage-collected between locales, reducing peak RSS
and slightly improving throughput due to reduced GC pressure.