Skip to content

few changes for discussion - #1180

Open
haydenflinner wants to merge 4 commits into
loro-dev:mainfrom
haydenflinner:main
Open

haydenflinner wants to merge 4 commits into
loro-dev:mainfrom
haydenflinner:main

Conversation

@haydenflinner

Copy link
Copy Markdown

Here are a few changes I've needed while building on Loro. I can submit them separately once we see which ones (if any) make sense to merge here.

  1. most risky: undo_span -- I don't want to revert the whole doc, I want to invert only the spans other peers wrote if I decide that I didn't want them to be able to do that. It seems to work, haven't written any torture tests for it though.
  2. seconds to milliseconds -- not sure why the precision was discarded, but it made it made my replays much more human-feeling to watch
  3. coalescing -- I had a 2MB page which was burning far too much time. Tracked it down to this splitting of fragments within a leaf's span. This change moved attach time from 13.5s to 9ms on my box, byte-identical exports.
  4. the tracing I implemented to figure this out, since everything was inlined

haydenflinner and others added 4 commits October 4, 2026 10:41
Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Rounds to seconds made per-commit timing useless for tape replay.
Renames merge_interval_in_s -> merge_interval_in_ms to match.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Fragments were hard-capped at 256 atoms, so a uniform ~1M-atom span
covered ~4k fragments. Each rope leaf split then re-mapped every
fragment in the span via update_insert_batch, giving
O(splits x span) — ~80M fragment iterations on a 2.2MB doc import
(~13s to attach, ~47s to re-export state).

Bound fragments by run count instead of atom count — a single-run
fragment has one cursor at any size:

- Cursor::try_merge joins two adjacent Small insert sets when their
  combined runs fit SMALL_SET_MAX_LEN, collapsing boundary runs that
  share a leaf.
- IdToCursor::coalesce merges adjacent compatible fragments in the
  range update_insert/update_insert_batch just touched. Adjacency is
  required (a.counter_end() == b.counter): the list can carry counter
  gaps left by other containers' ops, and merging across a gap would
  change get_insert results inside it.
- insert() no longer splits a large uniform single-run cursor into
  MAX_FRAGMENT_LEN chunks, and merges with the previous adjacent
  fragment when possible.

Result: the 2.2MB production doc attaches in ~9ms (was 13.4s), and
perf_import_insert_split_quadratic_e2e (2M atoms, 8192 boundary
splits, ~33.5M expected fragment updates before) now runs in ~15ms.
State exports verified byte-identical to the pre-fix build.
…acker

Adds an off-by-default `tracker-stats` cargo feature (loro +
loro-internal) with zero-cost `#[cfg]`-gated counters on the richtext
Tracker's hot paths:

- op counts: inserts, checkouts, retreat/forward elements
- skip_applied forwarding, Fugue in_between scans, leaf splits
- id→cursor work: per-fragment update iterations, update_many call
  totals, dense-array rebuilds, large-set updates, iterator yields,
  batch call counts, span atoms, and max fragment-list length
- stats::dump(tag) prints the totals; called at the end of
  calc_diff_internal and the richtext tracker rebuild

Written to quantify a pathological fragment-remapping case on a 2.2MB
document (~80M fragment iterations); kept because it makes the inner
loop magnitudes visible without recompiling instrumentation.

This branch has not been deployed

No deployments
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