Skip to content

Conversation

@brnrdog
Copy link
Owner

@brnrdog brnrdog commented Jan 21, 2026

Context

I run some benchmarks with rescript-signals using the js-reactivity-benchmark project. It didn't go well in many categories, so I decided to spend some time investing in improving.

This resulted in some small changes and other deep structural changes, especially in our internal data structures.

The API and expected behaviours remain the same.

Performance Improvements

The following are results from a custom benchmark script, also included in this pull request:

┌────────────────────────────────────┬───────────┬─────────────────────┬──────────────┐
│             Benchmark              │   main    │ perf-improvements-1 │   Speedup    │
├────────────────────────────────────┼───────────┼─────────────────────┼──────────────┤
│ Signal Creation (10k signals)      │ 726.22ms  │ 35.10ms             │ 20.7x faster │
├────────────────────────────────────┼───────────┼─────────────────────┼──────────────┤
│ Computed Creation (10k computeds)  │ 3973.45ms │ 148.91ms            │ 26.7x faster │
├────────────────────────────────────┼───────────┼─────────────────────┼──────────────┤
│ Deep Computed Chain (100 depth)    │ 14.27ms   │ 1.73ms              │ 8.2x faster  │
├────────────────────────────────────┼───────────┼─────────────────────┼──────────────┤
│ Signal Updates (100 observers)     │ 373.05ms  │ 64.23ms             │ 5.8x faster  │
├────────────────────────────────────┼───────────┼─────────────────────┼──────────────┤
│ Wide Dependency Tree (100 sources) │ 222.22ms  │ 123.17ms            │ 1.8x faster  │
├────────────────────────────────────┼───────────┼─────────────────────┼──────────────┤
│ Batched Updates (100 signals)      │ 271.01ms  │ 141.60ms            │ 1.9x faster  │
├────────────────────────────────────┼───────────┼─────────────────────┼──────────────┤
│ Effects (single trigger)           │ 11.37ms   │ 5.15ms              │ 2.2x faster  │
└────────────────────────────────────┴───────────┴─────────────────────┴──────────────┘

The most dramatic improvements are in creation paths (20-27x faster) due to eliminating the separate observer object
allocation for computeds. The signal update path is also significantly faster (5.8x) from avoiding Map lookups and
pointer indirection during dirty propagation.

1. Level calculation (Signals__Scheduler.res:150-199)
  - Replaced array allocation + reduce with mutable ref for max tracking
  - Before: let computedLevels = [] → Array.push → Array.reduce
  - After: let maxLevel = ref(0) → inline comparison
2. Flush function (Signals__Scheduler.res:234-261)
  - Eliminated O(n log n) Map lookups during sort
  - Pre-allocated pendingComputeds and pendingEffects arrays
  - Added efficient clearArray using arr.length = 0
  - Separate sorting for computeds vs effects (simpler comparator)
3. addDep function (Signals__Scheduler.res:116-129)
  - Removed redundant isCurrentObserver check
  - Moved SignalObservers.ensure inside the "new dep" branch
  - Reduced operations on the hot path
- Add backingSubs field to observer for direct
  dirty propagation (no Map lookup)
- Remove computedSubs Map entirely
- Skip computeLevel on subsequent runs when
  level is already set
- 87% improvement on signal updates benchmark
Add needsSort check to avoid O(n log n) sort overhead
when all pending observers have the same level, which
is common for flat dependency structures.
- Skip clearDeps on fresh observer (no deps yet)
- Defer computeLevel to first retrack (lazy)
- Remove try/catch from creation hot path
- Add makeForComputed for optimized backing signal
@brnrdog brnrdog force-pushed the perf-improvements-1 branch from 35884de to 89c8b2d Compare January 21, 2026 21:16
@brnrdog brnrdog changed the title performance improvements performance improvements 1 Jan 21, 2026
@brnrdog brnrdog merged commit cfc2a7c into main Jan 21, 2026
2 checks passed
@github-actions
Copy link

🎉 This PR is included in version 1.3.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

@brnrdog brnrdog deleted the perf-improvements-1 branch January 22, 2026 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants