Logo Mibo

Performance

The first thing to internalize: using adaptive data is rarely the bottleneck. In a live simulation-shaped game (several systems, a spatial join per entity, HUD values that follow the world every frame) the adaptive machinery runs at a small fraction of a millisecond per frame at 60 fps. The frame's real cost is almost always elsewhere: the GPU, the draw batch, vsync. A healthy graph is close to free.

Where the cost actually comes from, in order of likelihood:

  1. Naive construction: rebuilding a node per frame, or a hand-rolled join that re-scans instead of using the operator.
  2. Allocation on the hot path: force/toMap called every frame, or nodes created inside update.
  3. The drawing phase: not the graph at all.

The library is built for tight-loop work, and its behavior there is something you can rely on:

If allocation matters to you, measure it rather than trusting anyone's claims: wrap a settled read in GC.GetAllocatedBytesForCurrentThread before and after, and look at the difference.

How much a join costs

AMap.joinOn (joining two maps on the same key) is cheap on writes: when one entry changes, only that entry's subgraph updates.

mapA whose mapping function reads another adaptive collection is the one shape to watch. When the inner collection changes, every element's mapping re-runs, so the cost is linear in the outer collection, and it re-runs as often as the inner one changes. That's not a bug; it's what the shape does. In practice a live simulation-shaped game with a per-entity join still keeps its adaptive work at a small fraction of a millisecond per frame at 60 fps. Joins are fine.

The shape that stops paying is a join over inputs that change every frame. Mixing positions or time (which move constantly) into a join means the rescan never settles, and it grows with the collections. When that shows up in a profile, don't keep paying: derive from a single collection, or compute the pairing in a plain loop where you control the cost directly. The projection is a convenience for reads, not a rule that everything must stay derived.

The benchmarks compare the combinators against FSharp.Data.Adaptive at several scales, if you want numbers.

Reading patterns

What the library does not do

If you need those, that's the FSharp.Data.Adaptive signal.

Type something to start searching.