Skip to content

perf: fix O(n²) bottlenecks in simplification and YAML serialization#307

Merged
GLips merged 5 commits intomainfrom
feat/benchmark-simplify
Mar 24, 2026
Merged

perf: fix O(n²) bottlenecks in simplification and YAML serialization#307
GLips merged 5 commits intomainfrom
feat/benchmark-simplify

Conversation

@GLips
Copy link
Copy Markdown
Owner

@GLips GLips commented Mar 24, 2026

Summary

Profiled the simplifyRawFigmaObject pipeline and fixed two independent O(n²) performance bottlenecks, then added tooling to catch future regressions.

1. Style lookup cache

findOrCreateVar was doing an O(n) linear scan with JSON.stringify on both sides for every style lookup — effectively O(n²) across all nodes. Replaced with a WeakMap-based reverse cache for O(1) lookups.

Simplification time Small file (1.5 MB / 483 nodes) Large file (71.6 MB / 30k nodes)
Before 57 ms 107.2 s
After 9 ms 120 ms
Speedup 6x 770x

2. YAML serialization options

js-yaml's dump() defaults include O(n²) reference detection, line-folding regex, and legacy compat checks. Since our output goes to LLMs, not human editors, we disable all of these.

YAML serialization Small file (1.5 MB / 483 nodes) Large file (71.6 MB / 30k nodes)
Before 11.8 ms 713 ms
After 9.6 ms 188 ms
Speedup 1.2x 3.8x

The speedup scales superlinearly with file size because both bottlenecks were O(n²).

3. Benchmark script

Added scripts/benchmark-simplify.ts (pnpm benchmark:simplify) that reports:

  • Per-extractor timing breakdown with percentages
  • Wall time for YAML/JSON serialization
  • Node counts (input → walked → output)
  • Memory usage (RSS, heap)
  • Optional --profile flag for CPU profiling via Chrome DevTools

Other changes

  • Export SimplifiedNode from the extractors barrel (src/extractors/index.ts)
  • Regression test for style deduplication behavior

Test plan

  • All 51 tests pass
  • New deduplication test verifies identical styles produce the same varId
  • Benchmark produces consistent results across multiple runs
  • Verified on both small (1.5 MB / 483 nodes) and large (71.6 MB / 30k nodes) files

GLips added 5 commits March 24, 2026 11:00
Profiles simplifyRawFigmaObject + serialization, reporting wall time,
memory usage, node counts, and output size. Supports --profile flag
for CPU profiling via Chrome DevTools.
Shows cumulative time and percentage for each extractor, afterChildren,
and walk/yield overhead. Also addresses review feedback: consolidate
imports through barrel, remove section-divider comments, fix RSS label.
Replace O(n) linear scan with JSON.stringify on both sides per lookup
with a Map-based reverse cache (serialized value → varId). The cache
is scoped per GlobalVars instance via WeakMap, so it's automatically
tied to each extraction run.

On a 71.6 MB / 30k node file: 107s → 139ms for simplification.
Verifies that two nodes with identical fills reference the same global
variable, ensuring the cache-based deduplication produces correct results.
- noRefs: true — skip O(n²) reference detection (no shared refs in output)
- lineWidth: -1 — skip line-folding regex (output goes to LLMs)
- noCompatMode: true — skip YAML 1.0/1.1 compat checks per string
- schema: JSON_SCHEMA — fewer implicit type resolvers per string
@GLips GLips changed the title perf: 770x faster design simplification via style lookup cache perf: optimize design simplification pipeline (770x) and YAML serialization (3.8x) Mar 24, 2026
@GLips GLips changed the title perf: optimize design simplification pipeline (770x) and YAML serialization (3.8x) perf: fix O(n²) bottlenecks in simplification and YAML serialization Mar 24, 2026
@GLips GLips merged commit 29cff0c into main Mar 24, 2026
1 check passed
@GLips GLips deleted the feat/benchmark-simplify branch March 24, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant