nx icon indicating copy to clipboard operation
nx copied to clipboard

perf(release,tasks-runner,generators): O(n²) to O(n) optimizations using Set/Map

Open adwait1290 opened this issue 2 weeks ago • 3 comments

Current Behavior

Several core Nx operations use O(n) array operations inside loops, resulting in O(n²) or O(n*m) time complexity:

  1. Release commands: releaseGraph.releaseGroups.find() called inside loops over sortedReleaseGroups
  2. Git tag filtering: Regex match() called twice on the same string
  3. Task runner terminal output: indexOf() used for task membership checks
  4. Task graph utils: O(n²) array deduplication pattern
  5. Project configuration: indexOf() used in filter for deleted files

Expected Behavior

All operations use optimal data structures:

  • Map for O(1) key-based lookups instead of Array.find()
  • Set for O(1) membership checks instead of Array.indexOf()
  • Set spread for O(n) deduplication instead of indexOf pattern
  • Variable caching to avoid duplicate regex operations

Summary of Changes

1. Release Commands (O(n²) → O(n))

┌─────────────────────────────────────────────────────────────┐
│ BEFORE                                                      │
│ for (name of sortedGroups) {           // O(n) iterations   │
│   releaseGroups.find(g => g.name)      // O(n) per lookup   │
│ }                                      // Total: O(n²)      │
├─────────────────────────────────────────────────────────────┤
│ AFTER                                                       │
│ groupMap = new Map(groups.map(g => [g.name, g]))  // O(n)   │
│ for (name of sortedGroups) {           // O(n) iterations   │
│   groupMap.get(name)                   // O(1) per lookup   │
│ }                                      // Total: O(n)       │
└─────────────────────────────────────────────────────────────┘

2. Git Tag Filtering (2x → 1x regex operations)

┌─────────────────────────────────────────────────────────────┐
│ BEFORE                                                      │
│ !!tag.match(regex) && tag.match(regex).some(...)  // 2 ops  │
├─────────────────────────────────────────────────────────────┤
│ AFTER                                                       │
│ matches = tag.match(regex); matches?.some(...)    // 1 op   │
└─────────────────────────────────────────────────────────────┘

3. Task Runner Terminal Output (O(n) → O(1) lookups)

┌─────────────────────────────────────────────────────────────┐
│ BEFORE                                                      │
│ tasks.indexOf(taskRow.task) > -1       // O(n) lookup       │
│ cachedTasks.indexOf(task) !== -1       // O(n) lookup       │
├─────────────────────────────────────────────────────────────┤
│ AFTER                                                       │
│ taskSet.has(taskRow.task)              // O(1) lookup       │
│ cachedTasks.has(task)                  // O(1) lookup       │
└─────────────────────────────────────────────────────────────┘

4. Task Graph Utils Deduplication (O(n²) → O(n))

┌─────────────────────────────────────────────────────────────┐
│ BEFORE                                                      │
│ arr.filter((item, i, arr) => arr.indexOf(item) === i)       │
│                              ↑ O(n) per element = O(n²)     │
├─────────────────────────────────────────────────────────────┤
│ AFTER                                                       │
│ [...new Set(arr)]            // O(n) total                  │
└─────────────────────────────────────────────────────────────┘

5. Project Configuration (O(n*m) → O(n))

┌─────────────────────────────────────────────────────────────┐
│ BEFORE                                                      │
│ projectFiles.filter(r => deletedFiles.indexOf(r) === -1)    │
│                          ↑ O(m) lookup per file = O(n*m)    │
├─────────────────────────────────────────────────────────────┤
│ AFTER                                                       │
│ deletedFilesSet = new Set(deletedFiles)                     │
│ projectFiles.filter(r => !deletedFilesSet.has(r)) // O(n)   │
└─────────────────────────────────────────────────────────────┘

Files Changed

  • packages/nx/src/command-line/release/release.ts - Map for release group lookup
  • packages/nx/src/command-line/release/version.ts - Map for release group lookup
  • packages/nx/src/command-line/release/publish.ts - Map for release group lookup
  • packages/nx/src/command-line/release/utils/git.ts - Cache regex match result
  • packages/nx/src/tasks-runner/life-cycles/dynamic-run-many-terminal-output-life-cycle.ts - Set for task lookup
  • packages/nx/src/tasks-runner/life-cycles/invoke-runner-terminal-output-life-cycle.ts - Set for cached/failed tasks
  • packages/nx/src/tasks-runner/task-graph-utils.ts - Set-based deduplication
  • packages/nx/src/generators/utils/project-configuration.ts - Set for deleted file lookup

Related Issue(s)

Performance optimization - no specific issue.

Merge Dependencies

Must be merged AFTER: #33737 Must be merged BEFORE: #33748


adwait1290 avatar Dec 08 '25 05:12 adwait1290

Deploy request for nx-docs pending review.

Visit the deploys page to approve it

Name Link
Latest commit 9611ee9df282bee557037c154504fa3c0d25daf2

netlify[bot] avatar Dec 08 '25 05:12 netlify[bot]

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
nx-dev Ready Ready Preview Dec 8, 2025 5:35am

vercel[bot] avatar Dec 08 '25 05:12 vercel[bot]

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@adwait1290 We are very grateful for your enthusiasm to contribute, I kindly request that you please stop sending these AI assisted micro-perf PRs now. In future, please open an issue regarding your plans and do not simply send pages worth of micro PRs without open communication.

Upon deeper inspection in some cases, we have found that they are not resulting in real-world performance wins, and instead create regressions because they are not considering memory and GC overhead of the whole system.

We will work on better benchmarking infrastructure on our side to have greater confidence in CI as to whether these kinds of PRs are actually net wins but for now each individual PR requires a thorough investigation by the team and you are sending far, far too many.

To reduce noise on the repo, I am going to close this, but rest assured it will be looked at as part of our performance optimization and benchmarking effort and merged in if it creates a provable net win.

Thank you once again for your keenness to help make Nx the best it can be, we really appreciate it!

JamesHenry avatar Dec 11 '25 10:12 JamesHenry

This pull request has already been merged/closed. If you experience issues related to these changes, please open a new issue referencing this pull request.

github-actions[bot] avatar Dec 17 '25 00:12 github-actions[bot]