Skip to content

perf: startup time investigation — proposal to close the gap with Node/Deno #68

Description

@sonukapoor

I've been investigating Edge's cold-start performance on the startup-investigation branch and wanted to share findings and get your thoughts on direction before going further.

What I measured

Using hyperfine --warmup 10 --runs 80 on frozen binaries:

Command Edge (current) Node.js Deno Bun
eval "" ~41ms ~40–50ms ~20–35ms ~5–12ms

Edge is already in range with Node. The gap to Deno is real and closeable. Bun is a different engine (JavaScriptCore) so that gap is architectural.

Root cause

The startup path was instrumented with EDGE_STARTUP_TRACE=1 and finer per-bootstrapper timing. The picture is clear:

  • ~19ms total is spent executing bootstrap JS on every cold start
  • ~8ms of that is bootstrap/node.js alone
  • No V8 startup snapshot exists — V8 parses and compiles all bootstrap JS fresh on every process launch
  • Micro-deferral experiments (lazy-loading individual modules) were tried and repeatedly reverted: they shift cost between trace buckets but don't eliminate it

The pre-JS host-side costs (OpenSSL init ~0.7ms, N-API env creation ~1.2ms) are comparatively small. The real leverage is the 19ms of repeated JS compilation.

Two options I'd like your input on

Option A — V8 bytecode cache (lower risk, faster to ship)

Precompile all bootstrap JS to V8 bytecode at build time and embed alongside the source. On startup, V8 deserializes instead of parsing and compiling. Skips the majority of per-module compile overhead.

  • Estimated win: ~8–12ms off cold start
  • Effort: a few days
  • Risk: low — graceful fallback to source if cache is invalid

Option B — V8 startup snapshot (larger win, more involved)

After running all bootstrap JS during a dedicated build step, serialize the V8 heap. Embed the blob in the binary. On startup, deserialize instead of executing any bootstrap JS at all. This is what Node.js and Deno do.

  • Estimated win: ~15–18ms off cold start
  • Effort: a few weeks
  • Risk: medium — requires identifying which bootstrap code is snapshot-safe, build pipeline changes

What's already landed on this branch

Three small wins are already in place:

  • BuildEffectiveCliState() skips cwd resolution when no --env-file/--experimental-config-file flags are present (~1.7% improvement on -e "")
  • Report binding creation deferred to first use
  • Native --help fast path

My ask

@syrusakbary

I'd like to own this area. Before going further I wanted to check:

  1. Does this direction (closing the startup gap with Deno) align with your priorities?
  2. Between Option A and B — which scope feels right?
  3. Any constraints I should be aware of (build pipeline, snapshot safety requirements, platform targets)?

Happy to start with a spike on Option A to get concrete numbers before committing to either approach fully.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions