| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 41b3538 commit 90ee193
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -70,6 +70,13 @@ const SMALL_FILE_FLOOR_BYTES = 100 * 1024; | |||
| 70 | 70 | */ | |
| 71 | 71 | const DOMINANT_FILE_RATIO = 0.7; | |
| 72 | 72 | ||
| 73 | + /** | ||
| 74 | + * Maximum number of worker threads to allocate concurrently for a single dominant file. | ||
| 75 | + * Capping concurrency on dominant files (which hold large AST and sourcemap trees) | ||
| 76 | + * prevents V8 heap and RSS explosion on machines with high CPU core counts. | ||
| 77 | + */ | ||
| 78 | + const MAX_DOMINANT_WORKERS = 4; | ||
| 79 | + | ||
| 73 | 80 | /** | |
| 74 | 81 | * Serializes the translation messages for a locale for transfer to an inliner Worker. | |
| 75 | 82 | * | |
@@ -522,14 +529,17 @@ export class I18nInliner { | |||
| 522 | 529 | ||
| 523 | 530 | let localesPerBatch: number; | |
| 524 | 531 | if (uncachedByFile.size === 1) { | |
| 525 | - // Single file in window: shard across all workers to avoid idle threads | ||
| 526 | - localesPerBatch = Math.max(1, Math.ceil(entries.length / workerCount)); | ||
| 532 | + // Single file in window: shard across bounded workers to balance throughput and peak memory | ||
| 533 | + const targetWorkers = Math.min(workerCount, MAX_DOMINANT_WORKERS); | ||
| 534 | + localesPerBatch = Math.max(1, Math.ceil(entries.length / targetWorkers)); | ||
| 527 | 535 | } else if (fileSize < SMALL_FILE_FLOOR_BYTES) { | |
| 528 | 536 | // Small chunks (< 100 KB): process all locales in 1 batch to eliminate IPC overhead | |
| 529 | 537 | localesPerBatch = entries.length; | |
| 530 | 538 | } else if (fileSize >= maxFileSize * DOMINANT_FILE_RATIO) { | |
| 531 | - // Dominant file(s): shard across all workers for maximum multi-core parallelism | ||
| 532 | - localesPerBatch = Math.max(1, Math.ceil(entries.length / workerCount)); | ||
| 539 | + // Dominant file(s) with multiple files: shard across bounded workers while always reserving | ||
| 540 | + // at least one worker to process smaller chunks in parallel without starvation. | ||
| 541 | + const targetWorkers = Math.max(1, Math.min(workerCount - 1, MAX_DOMINANT_WORKERS)); | ||
| 542 | + localesPerBatch = Math.max(1, Math.ceil(entries.length / targetWorkers)); | ||
| 533 | 543 | } else { | |
| 534 | 544 | // Intermediate files: moderate sharding | |
| 535 | 545 | localesPerBatch = Math.max(1, Math.ceil(entries.length / 2)); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -311,6 +311,92 @@ export function createMonolithicScenario(options: ScenarioFactoryOptions = {}): | |||
| 311 | 311 | }; | |
| 312 | 312 | } | |
| 313 | 313 | ||
| 314 | + /** | ||
| 315 | + * 5. Multi-Dominant Scenario: | ||
| 316 | + * 3 dominant bundles (3 MB each, e.g. main.js, polyfills.js, admin-portal.js) + 15 small chunks (40 KB), | ||
| 317 | + * 8 locales, sourcemaps ON. | ||
| 318 | + * Tests contention and worker distribution when multiple large files qualify as dominant in the same window. | ||
| 319 | + */ | ||
| 320 | + export function createMultiDominantScenario( | ||
| 321 | + options: ScenarioFactoryOptions = {}, | ||
| 322 | + ): BenchmarkScenario { | ||
| 323 | + let workload: GeneratedWorkload | undefined; | ||
| 324 | + | ||
| 325 | + return { | ||
| 326 | + name: 'multi-dominant', | ||
| 327 | + description: | ||
| 328 | + 'Multi-Dominant: 3 large bundles (3 MB each) + 15 chunks (40 KB), 8 locales, sourcemaps ON', | ||
| 329 | + get inputSizeBytes() { | ||
| 330 | + return workload?.totalInputSizeBytes ?? 0; | ||
| 331 | + }, | ||
| 332 | + get localeCount() { | ||
| 333 | + return DEFAULT_LOCALES_8.length; | ||
| 334 | + }, | ||
| 335 | + async setup() { | ||
| 336 | + await initializeFixtures(); | ||
| 337 | + const files: BuildOutputFile[] = []; | ||
| 338 | + const totalMessages = 3000; | ||
| 339 | + const dominantCount = 3; | ||
| 340 | + const dominantSize = 3 * 1024 * 1024; | ||
| 341 | + const dominantMessages = 600; // 600 each x 3 = 1800 messages | ||
| 342 | + | ||
| 343 | + for (let d = 0; d < dominantCount; d++) { | ||
| 344 | + const { codeFile, mapFile } = generateSyntheticBundle({ | ||
| 345 | + filename: `dominant_${d}.js`, | ||
| 346 | + targetByteSize: dominantSize, | ||
| 347 | + messageCount: dominantMessages, | ||
| 348 | + withSourceMap: true, | ||
| 349 | + messageIdOffset: d * dominantMessages, | ||
| 350 | + }); | ||
| 351 | + files.push(codeFile); | ||
| 352 | + if (mapFile) { | ||
| 353 | + files.push(mapFile); | ||
| 354 | + } | ||
| 355 | + } | ||
| 356 | + | ||
| 357 | + const chunkCount = 15; | ||
| 358 | + const chunkSizeBytes = 40 * 1024; | ||
| 359 | + const remainingMessages = totalMessages - dominantCount * dominantMessages; | ||
| 360 | + const messagesPerChunk = Math.max(1, Math.floor(remainingMessages / chunkCount)); | ||
| 361 | + | ||
| 362 | + for (let i = 0; i < chunkCount; i++) { | ||
| 363 | + const { codeFile, mapFile } = generateSyntheticBundle({ | ||
| 364 | + filename: `chunk_${i}.js`, | ||
| 365 | + targetByteSize: chunkSizeBytes, | ||
| 366 | + messageCount: messagesPerChunk, | ||
| 367 | + withSourceMap: true, | ||
| 368 | + messageIdOffset: dominantCount * dominantMessages + i * messagesPerChunk, | ||
| 369 | + }); | ||
| 370 | + files.push(codeFile); | ||
| 371 | + if (mapFile) { | ||
| 372 | + files.push(mapFile); | ||
| 373 | + } | ||
| 374 | + } | ||
| 375 | + | ||
| 376 | + const locales = generateTranslations(DEFAULT_LOCALES_8, totalMessages); | ||
| 377 | + workload = { | ||
| 378 | + files, | ||
| 379 | + locales, | ||
| 380 | + totalInputSizeBytes: calculateInputSizeBytes(files), | ||
| 381 | + }; | ||
| 382 | + }, | ||
| 383 | + async run() { | ||
| 384 | + if (!workload) { | ||
| 385 | + return; | ||
| 386 | + } | ||
| 387 | + const inliner = new I18nInliner({ | ||
| 388 | + missingTranslation: 'warning', | ||
| 389 | + maxConcurrency: options.concurrency, | ||
| 390 | + }); | ||
| 391 | + try { | ||
| 392 | + await inliner.inlineAll(workload.files, workload.locales); | ||
| 393 | + } finally { | ||
| 394 | + await inliner.close(); | ||
| 395 | + } | ||
| 396 | + }, | ||
| 397 | + }; | ||
| 398 | + } | ||
| 399 | + | ||
| 314 | 400 | /** | |
| 315 | 401 | * Helper to prime the persistent cache in an isolated process. | |
| 316 | 402 | */ | |
@@ -462,6 +548,7 @@ export function getAllScenarios(options: ScenarioFactoryOptions = {}): Benchmark | |||
| 462 | 548 | createEnterpriseScenario(options), | |
| 463 | 549 | createLargeEnterpriseScenario(options), | |
| 464 | 550 | createMonolithicScenario(options), | |
| 551 | + createMultiDominantScenario(options), | ||
| 465 | 552 | createPersistentCacheWarmScenario(options), | |
| 466 | 553 | ]; | |
| 467 | 554 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments