Skip to content

Nudge is 14x slower than draft translate at 50K points #36

@kostyafarber

Description

@kostyafarber

Problem

At 50K points, nudge (arrow key) takes 34ms p50 per frame while draft translate takes 2.4ms p50 — a 14x difference for what should be similar work.

Measured via Playwright E2E perf tests on the real Electron app with GPU acceleration.

Root Cause

NudgePointsCommand.execute() goes through the full sync pipeline on every keypress:

  1. Glyphs.findPoints(glyph, pointIds) — iterates ALL 50K points to look up each ID via Set. ~5-8ms
  2. .map() to build updates — allocates 50K position update objects. ~2-3ms
  3. glyph.apply(updates) — JS-side position patch (same as draft). ~2ms
  4. this.#$glyph.set(glyph) — fires glyph identity signal, triggers more downstream effects than the contour-only signals draft uses
  5. #syncPositions(updates) — marshals 50K IDs + coords into Float64Arrays, crosses NAPI boundary to Rust. ~10-15ms
  6. commandHistory.execute() — records an undo entry on every single frame

The draft pattern avoids steps 1, 2, 4, 5, 6 entirely — it only calls glyph.apply() per frame and defers the Rust sync to finish().

Proposed Fix

Use the draft pattern for nudge: accumulate position changes in JS, only sync to Rust and record undo when the arrow key is released (or after a debounce). This is the same pattern used for translate/resize/rotate drag operations.

Perf Report (50K points)

Operation p50 (ms) p95 (ms) p99 (ms)
translate-drag (5 pts) 0.40 0.50 0.70
translate-drag (1K pts) 0.60 0.70 1.00
translate-drag (all pts) 2.40 7.90 12.70
nudge (all pts) 34.20 41.40 45.50
undo (all pts) 30.90 32.40 33.50
pen-tool (100 clicks) 81.90 405.70 407.30

Files

  • src/renderer/src/lib/commands/primitives/BezierCommands.tsNudgePointsCommand
  • src/renderer/src/lib/editor/Editor.ts:1377nudgePoints()
  • src/renderer/src/bridge/NativeBridge.ts:312setNodePositions() (the full sync path)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions