Skip to main content

krabka_bench_driver/soak/
ledger.rs

1//! Sequence sets: the compact form every soak ledger keeps its records in.
2//!
3//! A sustained-load run acknowledges hundreds of millions of records. Holding
4//! each one as its own entry would make the ledger the largest thing on the
5//! load host, so each producer numbers its records `0, 1, 2, ...` and the
6//! ledgers store those numbers as sorted, disjoint, half-open runs. A producer
7//! that acks in order costs one run however long it runs, and a reader that
8//! sees partitions interleaved costs a run per gap until the gaps close.
9
10use serde::{Deserialize, Serialize};
11
12/// A set of `u64` sequence numbers, held as sorted, disjoint, half-open runs.
13///
14/// Adjacent runs are always merged, so two sets with the same members have the
15/// same runs and compare equal. The JSON form is the run list itself:
16/// `[[0, 10], [12, 13]]` is `{0, ..., 9, 12}`.
17#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(from = "Vec<[u64; 2]>", into = "Vec<[u64; 2]>")]
19pub struct SeqSet {
20    runs: Vec<[u64; 2]>,
21}
22
23impl SeqSet {
24    /// The empty set.
25    #[must_use]
26    pub fn new() -> Self { Self::default() }
27
28    /// The set `start..end`.
29    #[must_use]
30    pub fn range(start: u64, end: u64) -> Self {
31        let mut set = Self::new();
32        set.insert_range(start, end);
33        set
34    }
35
36    /// Add `seq`, and say whether it was new.
37    ///
38    /// A reader uses the answer to spot a record it has already seen.
39    pub fn insert(&mut self, seq: u64) -> bool { self.insert_range(seq, seq.saturating_add(1)) > 0 }
40
41    /// Add every member of `start..end`, and return how many were new.
42    pub fn insert_range(&mut self, start: u64, end: u64) -> u64 {
43        if start >= end {
44            return 0;
45        }
46        // The runs that overlap or touch `start..end`: every run from the
47        // first that does not end before `start` to the last that does not
48        // begin after `end`.
49        let low = self.runs.partition_point(|run| run[1] < start);
50        let high = self.runs.partition_point(|run| run[0] <= end);
51        let overlapping = self.runs.get(low..high).unwrap_or_default();
52        let already: u64 = overlapping
53            .iter()
54            .map(|run| run[1].min(end).saturating_sub(run[0].max(start)))
55            .sum();
56        let merged = match overlapping {
57            [first, .., last] => [first[0].min(start), last[1].max(end)],
58            [only] => [only[0].min(start), only[1].max(end)],
59            [] => [start, end],
60        };
61        self.runs.splice(low..high, [merged]);
62        (end - start) - already
63    }
64
65    /// Remove `seq`, and say whether it was a member.
66    pub fn remove(&mut self, seq: u64) -> bool {
67        let index = self.runs.partition_point(|run| run[1] <= seq);
68        let Some(run) = self.runs.get(index).copied() else {
69            return false;
70        };
71        if seq < run[0] {
72            return false;
73        }
74        let pieces = [[run[0], seq], [seq + 1, run[1]]];
75        self.runs.splice(
76            index..=index,
77            pieces.into_iter().filter(|piece| piece[0] < piece[1]),
78        );
79        true
80    }
81
82    /// Whether `seq` is a member.
83    #[must_use]
84    pub fn contains(&self, seq: u64) -> bool {
85        let index = self.runs.partition_point(|run| run[1] <= seq);
86        self.runs.get(index).is_some_and(|run| run[0] <= seq)
87    }
88
89    /// How many members the set has.
90    #[must_use]
91    pub fn len(&self) -> u64 { self.runs.iter().map(|run| run[1] - run[0]).sum() }
92
93    /// Whether the set has no members.
94    #[must_use]
95    pub fn is_empty(&self) -> bool { self.runs.is_empty() }
96
97    /// The largest member.
98    #[must_use]
99    pub fn last(&self) -> Option<u64> { self.runs.last().map(|run| run[1] - 1) }
100
101    /// Every member, in ascending order. Reverse it to walk down from the
102    /// largest member.
103    pub fn iter(&self) -> impl DoubleEndedIterator<Item = u64> + '_ {
104        self.runs.iter().flat_map(|run| run[0]..run[1])
105    }
106
107    /// Every member of `self` or `other`.
108    ///
109    /// One merge over the two run lists, so the cost is linear in their runs.
110    #[must_use]
111    pub fn union(&self, other: &Self) -> Self {
112        let mut out = Self::new();
113        let (mut left, mut right) = (self.runs.iter().peekable(), other.runs.iter().peekable());
114        loop {
115            let next = match (left.peek(), right.peek()) {
116                (Some(a), Some(b)) if a[0] <= b[0] => left.next(),
117                (_, Some(_)) => right.next(),
118                (Some(_), None) => left.next(),
119                (None, None) => break,
120            };
121            if let Some(&run) = next {
122                out.push_run(run);
123            }
124        }
125        out
126    }
127
128    /// Every member of any of `sets`.
129    ///
130    /// It collects every run, sorts them once and merges them, so it costs
131    /// `O(R log R)` in the total run count `R`. Folding [`Self::union`] over
132    /// many sets costs a merge per set instead.
133    #[must_use]
134    pub fn union_all<'a>(sets: impl IntoIterator<Item = &'a Self>) -> Self {
135        sets.into_iter()
136            .flat_map(|set| set.runs.iter().copied())
137            .collect::<Vec<_>>()
138            .into()
139    }
140
141    /// Every member of `self` that is not a member of `other`.
142    ///
143    /// One merge over the two run lists, so the cost is linear in their runs.
144    #[must_use]
145    pub fn difference(&self, other: &Self) -> Self {
146        let mut out = Self::new();
147        // `first` is the first run of `other` that ends after the current run
148        // of `self` starts. It only moves forward, and each run of `other`
149        // past it is visited for at most two runs of `self`: the runs of
150        // `self` are disjoint, so a cut that reaches past one run is the last
151        // cut that run sees.
152        let mut first = 0;
153        for &[start, end] in &self.runs {
154            while other.runs.get(first).is_some_and(|cut| cut[1] <= start) {
155                first += 1;
156            }
157            let mut cursor = start;
158            for cut in other.runs.iter().skip(first).take_while(|cut| cut[0] < end) {
159                if cut[0] > cursor {
160                    out.push_run([cursor, cut[0]]);
161                }
162                cursor = cursor.max(cut[1]);
163            }
164            if cursor < end {
165                out.push_run([cursor, end]);
166            }
167        }
168        out
169    }
170
171    /// Every member of both `self` and `other`.
172    ///
173    /// One merge over the two run lists, with at most one binary search per
174    /// run.
175    #[must_use]
176    pub fn intersection(&self, other: &Self) -> Self {
177        let mut out = Self::new();
178        let (mut left, mut right) = (0, 0);
179        while let (Some(a), Some(b)) = (self.runs.get(left), other.runs.get(right)) {
180            let overlap = [a[0].max(b[0]), a[1].min(b[1])];
181            if overlap[0] < overlap[1] {
182                out.push_run(overlap);
183            }
184            // Drop the run that ends first: nothing after it can overlap it.
185            // A side whose runs all end before the other's current run starts
186            // skips them in one binary search, so a small set intersected
187            // with a large one costs a search per small run, not a step per
188            // large run.
189            if a[1] <= b[0] {
190                left += self
191                    .runs
192                    .get(left..)
193                    .map_or(0, |rest| rest.partition_point(|run| run[1] <= b[0]));
194            } else if b[1] <= a[0] {
195                right += other
196                    .runs
197                    .get(right..)
198                    .map_or(0, |rest| rest.partition_point(|run| run[1] <= a[0]));
199            } else if a[1] <= b[1] {
200                left += 1;
201            } else {
202                right += 1;
203            }
204        }
205        out
206    }
207
208    /// Append `run`, which must start no earlier than the last run starts,
209    /// and merge it into the last run if the two overlap or touch.
210    ///
211    /// Every merge above feeds runs in start order, so this keeps the runs
212    /// sorted, disjoint and merged in constant time per run.
213    fn push_run(&mut self, run: [u64; 2]) {
214        if run[0] >= run[1] {
215            return;
216        }
217        match self.runs.last_mut() {
218            Some(last) if run[0] <= last[1] => last[1] = last[1].max(run[1]),
219            _ => self.runs.push(run),
220        }
221    }
222
223    /// The first `count` members, for a failure message that names examples
224    /// without printing a whole ledger.
225    #[must_use]
226    pub fn sample(&self, count: usize) -> Vec<u64> { self.iter().take(count).collect() }
227}
228
229/// Reading a run list back canonicalizes it: overlapping, touching or unsorted
230/// runs merge exactly as inserting them would.
231impl From<Vec<[u64; 2]>> for SeqSet {
232    fn from(mut runs: Vec<[u64; 2]>) -> Self {
233        runs.sort_unstable();
234        let mut set = Self::new();
235        for run in runs {
236            set.push_run(run);
237        }
238        set
239    }
240}
241
242impl From<SeqSet> for Vec<[u64; 2]> {
243    fn from(set: SeqSet) -> Self { set.runs }
244}
245
246impl FromIterator<u64> for SeqSet {
247    fn from_iter<I: IntoIterator<Item = u64>>(iter: I) -> Self {
248        let mut set = Self::new();
249        for seq in iter {
250            set.insert(seq);
251        }
252        set
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use {
259        super::SeqSet,
260        assert2::{assert, check},
261        std::collections::BTreeSet,
262    };
263
264    /// The same set, one member at a time: the model the run arithmetic is
265    /// checked against.
266    fn members(set: &SeqSet) -> BTreeSet<u64> { set.iter().collect() }
267
268    fn runs(list: &[[u64; 2]]) -> SeqSet { SeqSet::from(list.to_vec()) }
269
270    #[yare::parameterized(
271        ascending = { &[0, 1, 2, 3], &[[0, 4]] },
272        descending = { &[3, 2, 1, 0], &[[0, 4]] },
273        gap_then_fill = { &[0, 2, 1], &[[0, 3]] },
274        two_runs = { &[0, 1, 5, 6], &[[0, 2], [5, 7]] },
275        interleaved = { &[10, 0, 11, 1, 12], &[[0, 2], [10, 13]] },
276        repeats = { &[4, 4, 4], &[[4, 5]] },
277    )]
278    fn inserts_merge_into_canonical_runs(inserted: &[u64], runs: &[[u64; 2]]) {
279        let set: SeqSet = inserted.iter().copied().collect();
280        check!(set.runs == runs);
281    }
282
283    #[test]
284    fn insert_reports_whether_a_member_was_new() {
285        let mut set = SeqSet::new();
286        check!(set.insert(7));
287        check!(!set.insert(7));
288        check!(set.insert(8));
289        check!(set.insert_range(0, 10) == 8);
290        check!(set.insert_range(5, 5) == 0);
291        check!(set.len() == 10);
292    }
293
294    #[yare::parameterized(
295        middle = { 5, true, &[[0, 5], [6, 10]] },
296        first = { 0, true, &[[1, 10]] },
297        last = { 9, true, &[[0, 9]] },
298        absent = { 12, false, &[[0, 10]] },
299    )]
300    fn remove_splits_a_run(seq: u64, was_member: bool, runs: &[[u64; 2]]) {
301        let mut set = SeqSet::range(0, 10);
302        check!(set.remove(seq) == was_member);
303        check!(set.runs == runs);
304        check!(!set.contains(seq));
305    }
306
307    #[yare::parameterized(
308        disjoint = { SeqSet::range(0, 5), SeqSet::range(10, 15), SeqSet::range(0, 5) },
309        covering = { SeqSet::range(0, 5), SeqSet::range(0, 10), SeqSet::new() },
310        hole = { SeqSet::range(0, 10), [4, 5].into_iter().collect(), [0, 1, 2, 3, 6, 7, 8, 9].into_iter().collect() },
311        overlapping_tail = { SeqSet::range(0, 10), SeqSet::range(8, 20), SeqSet::range(0, 8) },
312    )]
313    fn difference_keeps_only_the_left_side(left: SeqSet, right: SeqSet, expected: SeqSet) {
314        check!(left.difference(&right) == expected);
315    }
316
317    #[test]
318    fn union_and_intersection_agree_with_membership() {
319        let left: SeqSet = [1, 2, 3, 10].into_iter().collect();
320        let right: SeqSet = [3, 4, 10, 11].into_iter().collect();
321        check!(left.union(&right) == [1, 2, 3, 4, 10, 11].into_iter().collect());
322        check!(left.intersection(&right) == [3, 10].into_iter().collect());
323    }
324
325    #[test]
326    fn the_json_form_is_the_run_list() {
327        let set: SeqSet = [0, 1, 2, 9].into_iter().collect();
328        let json = serde_json::to_string(&set).unwrap();
329        assert!(json == "[[0,3],[9,10]]");
330        check!(serde_json::from_str::<SeqSet>(&json).unwrap() == set);
331        check!(set.last() == Some(9));
332        check!(set.sample(2) == vec![0, 1]);
333    }
334
335    #[test]
336    fn a_non_canonical_run_list_reads_back_canonical() {
337        let set: SeqSet = serde_json::from_str("[[5,8],[0,2],[2,5],[7,9]]").unwrap();
338        check!(set == SeqSet::range(0, 9));
339    }
340
341    #[yare::parameterized(
342        both_empty = { &[], &[] },
343        left_empty = { &[], &[[3, 8]] },
344        right_empty = { &[[3, 8]], &[] },
345        equal = { &[[3, 8], [10, 12]], &[[3, 8], [10, 12]] },
346        disjoint = { &[[0, 5]], &[[10, 15]] },
347        touching = { &[[0, 5]], &[[5, 10]] },
348        touching_both_sides = { &[[0, 5], [10, 15]], &[[5, 10]] },
349        overlapping = { &[[0, 8]], &[[5, 12]] },
350        nested = { &[[0, 20]], &[[5, 8], [10, 12]] },
351        nested_reversed = { &[[5, 8], [10, 12]], &[[0, 20]] },
352        cut_spans_two_runs = { &[[0, 4], [6, 10], [12, 14]], &[[2, 13]] },
353        many_runs_interleaved = { &[[0, 2], [4, 6], [8, 10], [12, 14], [16, 18]], &[[1, 5], [7, 8], [9, 13], [17, 30]] },
354        single_members = { &[[0, 1], [2, 3], [4, 5]], &[[1, 2], [3, 4], [5, 6]] },
355        far_apart = { &[[0, 1], [u64::MAX - 2, u64::MAX]], &[[1, 2], [u64::MAX - 1, u64::MAX]] },
356    )]
357    fn set_operations_match_a_member_by_member_model(left: &[[u64; 2]], right: &[[u64; 2]]) {
358        let (left, right) = (runs(left), runs(right));
359        let (model_left, model_right) = (members(&left), members(&right));
360        for (name, got, expected) in [
361            ("union", left.union(&right), &model_left | &model_right),
362            (
363                "difference",
364                left.difference(&right),
365                &model_left - &model_right,
366            ),
367            (
368                "intersection",
369                left.intersection(&right),
370                &model_left & &model_right,
371            ),
372            (
373                "union_all",
374                SeqSet::union_all([&left, &right]),
375                &model_left | &model_right,
376            ),
377        ] {
378            check!(members(&got) == expected, "{name}");
379            // Equal members and equal runs: the result is canonical.
380            check!(
381                got == expected.iter().copied().collect::<SeqSet>(),
382                "{name}"
383            );
384        }
385    }
386
387    /// Two sets of 200 000 runs each, every run of one between two runs of the
388    /// other. A quadratic union, difference or intersection would take
389    /// minutes here; the linear merges take milliseconds.
390    #[test]
391    fn set_operations_stay_linear_on_many_runs() {
392        const RUNS: u64 = 200_000;
393        let left = SeqSet::from((0..RUNS).map(|n| [4 * n, 4 * n + 2]).collect::<Vec<_>>());
394        let right = SeqSet::from(
395            (0..RUNS)
396                .map(|n| [4 * n + 1, 4 * n + 3])
397                .collect::<Vec<_>>(),
398        );
399        check!(
400            left.union(&right)
401                == SeqSet::from((0..RUNS).map(|n| [4 * n, 4 * n + 3]).collect::<Vec<_>>())
402        );
403        check!(left.union(&right).len() == 3 * RUNS);
404        check!(left.difference(&right).len() == RUNS);
405        check!(left.intersection(&right).len() == RUNS);
406        let many: Vec<SeqSet> = (0..RUNS).map(|n| SeqSet::range(2 * n, 2 * n + 1)).collect();
407        check!(SeqSet::union_all(&many).len() == RUNS);
408    }
409
410    #[test]
411    fn iter_walks_down_from_the_largest_member_in_reverse() {
412        let set = runs(&[[0, 2], [5, 7]]);
413        check!(set.iter().rev().collect::<Vec<_>>() == [6, 5, 1, 0]);
414    }
415}