1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
use LoroDoc;
use Instant;
/// Regression guard for the O(n^2) styled-read blow-up caused by cloning
/// `Styles` once per style range.
///
/// `RichtextState::iter` / `slice_delta` used to build the per-range
/// `StyleMeta` with `x.1.clone().into()`. `Styles` owns a
/// `BTreeSet<Arc<StyleOp>>` per key, holding *every* style op that covers the
/// range, while `StyleMeta` keeps only the LWW winner (`StyleValue::get`).
/// So each range deep-copied a set whose size grows with the number of marks
/// on the container, and then threw all but one element away: O(marks) per
/// range times O(marks) ranges. `From<&Styles> for StyleMeta` already takes a
/// reference, so the copy was pure waste.
///
/// Style anchors are never consolidated, so the marks accumulate in state and
/// every styled read pays for all of them. Measured here:
///
/// | accumulated marks | before | after |
/// |------------------:|--------:|------:|
/// | 500 | 4.9ms | 82us |
/// | 1000 | 22.6ms | 246us |
/// | 2000 | 103.2ms | 1.0ms |
///
/// This guards the *read* path only, so the growth is still superlinear: the
/// residual comes from `StyleRangeMap` materializing the full op set on every
/// element it covers, which is O(n^2) in memory (309MB at n=4000 for 724
/// visible chars) and is not addressed here.
///
/// Run with:
/// cargo test -p loro perf_styled_read_scales_with_accumulated_marks --release -- --ignored --nocapture