Skip to main content

yo_kv/
slab.rs

1//! Somewhere to keep a value that is not bytes, addressed by a small number.
2//!
3//! A string lives in the record: the map hands back the bytes and the bytes are
4//! the value, which is what makes `GET` one lookup and one cache miss. A set
5//! cannot do that. It owns three allocations that grow and shrink as members
6//! come and go, and rewriting the record on every `SADD` to keep them inline
7//! would be a copy of the whole set per member added.
8//!
9//! So the record holds four bytes saying where, and this is what those four
10//! bytes point into:
11//!
12//! ```text
13//!   record                     slab
14//! +------+-------+         +---+---+---+---+---+
15//! | meta | u32 3 | ------> | 0 | 1 | 2 | 3 | 4 |
16//! +------+-------+         +---+---+---+---+---+
17//!    kind says              free    ^   set
18//!    which slab                     |
19//!                          the number in the record
20//! ```
21//!
22//! That is a second cache miss on the way to a set, and there is no arrangement
23//! that avoids it, because the set is bigger than a record and lives longer than
24//! any one command. What it does avoid is a second *hash* and a second *lookup*:
25//! the number comes out of the record the key lookup already fetched, so the
26//! second miss is a dependent load and not another trip through the index.
27//!
28//! # One slab per type, not one slab of an enum
29//!
30//! There will be a slab of sets, then one of hashes, then lists and sorted sets.
31//! The alternative is one slab holding an enum over all of them, which would be
32//! one field instead of four, and it would make every slot as big as the largest
33//! type and put a discriminant check in front of every access. The type tag in
34//! the meta byte already says which type a key holds, so that check would be the
35//! second time the same question got asked. Four fields it is.
36//!
37//! # Reuse, and what happens if a number outlives its value
38//!
39//! Freed slots go on a free list threaded through the vacancies, so an insert
40//! after a delete costs the same as the first insert and the vector does not
41//! grow forever under a churning workload.
42//!
43//! That means a number can be handed out twice, and a stale number would read
44//! somebody else's value rather than nothing. There is no generation counter to
45//! catch it, and the reason is that the only way to hold a stale number is for a
46//! record to outlive the [`Slab::remove`] that freed it, which is one function
47//! putting the two in the wrong order. A generation would turn that bug into a
48//! `None` at the cost of eight bytes a record for every key of every type, which
49//! is paying forever for a mistake that is caught the first time it is made.
50//! What the slab does promise is that [`Slab::remove`] on a slot that is already
51//! free answers `None` and leaves the free list alone, so a double free is inert
52//! rather than a loop in the list.
53//!
54//! # Knowing what it costs without asking everything
55//!
56//! Adding up what the values hold means asking every one of them, and a server
57//! with a `maxmemory` has to know that number often enough that walking a
58//! million sets to find it is not an option. So the slab can be asked to keep a
59//! running total instead.
60//!
61//! The trick is that a value can only change through [`Slab::get_mut`], which is
62//! also the only way in, so the slab sees every collection that is about to
63//! move before it moves. When tracking is on it takes that value's bytes back
64//! out of the total and writes the slot down. Nothing is added back until
65//! somebody asks for the number, and then only the slots on that list are asked
66//! again. A batch touches a handful of collections, so reading the total costs a
67//! handful of questions rather than one per key.
68//!
69//! Tracking is off until something turns it on, and off it costs one predictable
70//! branch on the way into `get_mut`. A server with no memory limit never needs
71//! the number and never pays for it.
72
73use std::mem;
74
75/// What one value in a slab is holding, so the slab can keep a total of it.
76///
77/// This is the same `memory_bytes` every collection already had, named as a
78/// trait so the slab can ask without knowing which of them it is holding.
79pub trait Bytes {
80    /// Bytes this value holds, not counting the slot it sits in.
81    fn memory_bytes(&self) -> usize;
82}
83
84/// The number that means no slot, which is the end of the free list.
85const NONE: u32 = u32::MAX;
86
87/// The most slots there can be.
88///
89/// One short of the whole u32 range, because the top value is spoken for as the
90/// end of the free list.
91pub const MAX_SLOTS: usize = NONE as usize;
92
93/// A slot is a value or a step along the free list.
94#[derive(Debug)]
95enum Slot<T> {
96    Filled(T),
97    /// The next free slot, or [`NONE`].
98    Free(u32),
99}
100
101/// Values addressed by a small stable number.
102#[derive(Debug)]
103pub struct Slab<T> {
104    slots: Vec<Slot<T>>,
105    /// The first free slot, or [`NONE`] when every slot is filled.
106    free: u32,
107    /// How many slots are filled, which is not `slots.len()`.
108    len: usize,
109    /// Bytes held by the values in the slots that are not on `soiled`.
110    ///
111    /// Only a real number while `track` is on. Off, it is zero and nobody reads
112    /// it.
113    clean: usize,
114    /// Slots reached mutably since the last reading, whose bytes are therefore
115    /// not in `clean` and have to be asked for again.
116    soiled: Vec<u32>,
117    /// One bit a slot, set while that slot is on `soiled`.
118    ///
119    /// A bit and not a byte because this is one per collection key and the
120    /// memory bar counts. A list rather than a scan of the bits because a
121    /// reading has to cost what the batch touched and not what the slab holds.
122    mark: Vec<u64>,
123    /// Whether the three above are being kept up to date.
124    track: bool,
125}
126
127impl<T: Bytes> Slab<T> {
128    /// An empty slab, which has not allocated anything.
129    #[must_use]
130    pub fn new() -> Slab<T> {
131        Slab {
132            slots: Vec::new(),
133            free: NONE,
134            len: 0,
135            clean: 0,
136            soiled: Vec::new(),
137            mark: Vec::new(),
138            track: false,
139        }
140    }
141
142    /// An empty slab with room for `n` values before it grows.
143    #[must_use]
144    pub fn with_capacity(n: usize) -> Slab<T> {
145        Slab {
146            slots: Vec::with_capacity(n),
147            ..Slab::new()
148        }
149    }
150
151    /// How many values are in it.
152    #[inline]
153    pub fn len(&self) -> usize {
154        self.len
155    }
156
157    /// Whether there are none.
158    #[inline]
159    pub fn is_empty(&self) -> bool {
160        self.len == 0
161    }
162
163    /// Put `value` in and answer where it went.
164    ///
165    /// A free slot if there is one, and the end otherwise. Nothing moves, so
166    /// every number handed out before stays good.
167    ///
168    /// # Panics
169    ///
170    /// If there are already [`MAX_SLOTS`] slots. That is four billion values of
171    /// one type in one database, which is not a number a caller can reach by
172    /// accident, and the alternative is a `Result` on the hot path of every
173    /// `SADD` against a key that does not exist yet.
174    pub fn insert(&mut self, value: T) -> u32 {
175        if self.free != NONE {
176            let at = self.free as usize;
177            let Slot::Free(next) = self.slots[at] else {
178                unreachable!("the free list only ever points at free slots");
179            };
180            self.free = next;
181            // Before the value goes in, so the slot the total is told about is
182            // still the empty one and nothing gets taken out for a value that
183            // was never counted.
184            self.soil(at as u32);
185            self.slots[at] = Slot::Filled(value);
186            self.len += 1;
187            return at as u32;
188        }
189        assert!(self.slots.len() < MAX_SLOTS, "slab is full");
190        let at = self.slots.len() as u32;
191        self.soil(at);
192        self.slots.push(Slot::Filled(value));
193        self.len += 1;
194        at
195    }
196
197    /// The value at `at`, or `None` if that slot is free or does not exist.
198    #[inline]
199    pub fn get(&self, at: u32) -> Option<&T> {
200        match self.slots.get(at as usize) {
201            Some(Slot::Filled(v)) => Some(v),
202            _ => None,
203        }
204    }
205
206    /// The value at `at`, to be changed in place.
207    ///
208    /// This is the only way to change a value, which is what lets the running
209    /// total be a total rather than a guess: whatever the caller does with the
210    /// reference, the slab already knows it has to ask this slot again.
211    #[inline]
212    pub fn get_mut(&mut self, at: u32) -> Option<&mut T> {
213        if self.track && (at as usize) < self.slots.len() {
214            self.soil(at);
215        }
216        match self.slots.get_mut(at as usize) {
217            Some(Slot::Filled(v)) => Some(v),
218            _ => None,
219        }
220    }
221
222    /// Take the value at `at` out and free the slot.
223    ///
224    /// Answers `None` if the slot was already free, without touching the free
225    /// list, so freeing twice is inert rather than a loop.
226    pub fn remove(&mut self, at: u32) -> Option<T> {
227        if self.track && (at as usize) < self.slots.len() {
228            self.soil(at);
229        }
230        match self.slots.get_mut(at as usize) {
231            Some(slot @ Slot::Filled(_)) => {
232                let taken = mem::replace(slot, Slot::Free(self.free));
233                self.free = at;
234                self.len -= 1;
235                match taken {
236                    Slot::Filled(v) => Some(v),
237                    Slot::Free(_) => unreachable!("just matched on filled"),
238                }
239            }
240            _ => None,
241        }
242    }
243
244    /// Every value in it, in no order a caller should lean on.
245    ///
246    /// This is for counting bytes and for saving, both of which want all of
247    /// them and neither of which cares which came first.
248    pub fn iter(&self) -> impl Iterator<Item = &T> {
249        self.slots.iter().filter_map(|s| match s {
250            Slot::Filled(v) => Some(v),
251            Slot::Free(_) => None,
252        })
253    }
254
255    /// Drop everything and hand the memory back.
256    ///
257    /// This is `FLUSHALL`, where keeping a vector of four million free slots
258    /// around for a database the client just emptied would be the wrong answer.
259    pub fn clear(&mut self) {
260        self.slots = Vec::new();
261        self.free = NONE;
262        self.len = 0;
263        self.clean = 0;
264        self.soiled = Vec::new();
265        self.mark = Vec::new();
266    }
267
268    /// What the slots themselves cost, not counting what the values point at.
269    pub fn slot_bytes(&self) -> usize {
270        self.slots.capacity() * mem::size_of::<Slot<T>>()
271    }
272
273    /// The old name for [`Slab::slot_bytes`], which answered the same number.
274    ///
275    /// Kept because a patch release does not take a public item away, and there
276    /// are now three ways to ask a slab what it costs rather than one, so the
277    /// name that does not say which of the three it means had to go. It goes at
278    /// the next minor.
279    #[deprecated(since = "0.3.8", note = "renamed to slot_bytes")]
280    pub fn memory_bytes(&self) -> usize {
281        self.slot_bytes()
282    }
283
284    /// What the values are holding, asked of every one of them.
285    ///
286    /// The honest walk, for the places that want the number exactly and do not
287    /// care what it costs to get: `INFO memory`, `MEMORY USAGE` and the tests.
288    /// It does not touch the running total and does not need it to be on.
289    pub fn value_bytes(&self) -> usize {
290        self.iter().map(T::memory_bytes).sum()
291    }
292
293    /// The same number, asked only of the values that could have changed.
294    ///
295    /// With tracking on this asks the slots touched since the last call and no
296    /// others, which is what makes a `maxmemory` server able to afford the
297    /// question once a batch. With tracking off it is [`Slab::value_bytes`].
298    pub fn settled_bytes(&mut self) -> usize {
299        if !self.track {
300            return self.value_bytes();
301        }
302        while let Some(at) = self.soiled.pop() {
303            self.mark[at as usize / 64] &= !(1u64 << (at % 64));
304            if let Some(Slot::Filled(v)) = self.slots.get(at as usize) {
305                self.clean += v.memory_bytes();
306            }
307        }
308        self.clean
309    }
310
311    /// Start or stop keeping the running total.
312    ///
313    /// Starting costs one walk, because a total has to start from somewhere and
314    /// the slab may already be holding a million sets when the client sets the
315    /// limit. Stopping costs nothing and gives the two lists back.
316    ///
317    /// Setting it to what it already is does nothing at all, which matters
318    /// because `CONFIG SET maxmemory` on a server that already had one would
319    /// otherwise pay for that walk every time.
320    pub fn track_bytes(&mut self, on: bool) {
321        if on == self.track {
322            return;
323        }
324        self.track = on;
325        self.soiled = Vec::new();
326        self.mark = Vec::new();
327        self.clean = if on { self.value_bytes() } else { 0 };
328    }
329
330    /// Take a slot's bytes back out of the total and write the slot down.
331    ///
332    /// Called before the slot changes and not after, so the value it asks is the
333    /// one the total was told about. A slot already written down is left alone,
334    /// which is why the same key written sixty four times in a batch costs one
335    /// question and not sixty four.
336    #[inline]
337    fn soil(&mut self, at: u32) {
338        if !self.track {
339            return;
340        }
341        let word = at as usize / 64;
342        let bit = 1u64 << (at % 64);
343        if word >= self.mark.len() {
344            self.mark.resize(word + 1, 0);
345        }
346        if self.mark[word] & bit != 0 {
347            return;
348        }
349        self.mark[word] |= bit;
350        self.soiled.push(at);
351        if let Some(Slot::Filled(v)) = self.slots.get(at as usize) {
352            self.clean -= v.memory_bytes();
353        }
354    }
355}
356
357impl<T: Bytes> Default for Slab<T> {
358    fn default() -> Slab<T> {
359        Slab::new()
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    // Length and not capacity, so a test can say what it expects without
368    // knowing how a `Vec` doubles.
369    impl Bytes for String {
370        fn memory_bytes(&self) -> usize {
371            self.len()
372        }
373    }
374
375    impl Bytes for Vec<u8> {
376        fn memory_bytes(&self) -> usize {
377            self.len()
378        }
379    }
380
381    // For the tests that are about the free list rather than about bytes.
382    impl Bytes for i32 {
383        fn memory_bytes(&self) -> usize {
384            0
385        }
386    }
387
388    impl Bytes for u8 {
389        fn memory_bytes(&self) -> usize {
390            0
391        }
392    }
393
394    #[test]
395    fn a_new_slab_holds_nothing_and_has_allocated_nothing() {
396        let s: Slab<String> = Slab::new();
397        assert_eq!(s.len(), 0);
398        assert!(s.is_empty());
399        assert_eq!(s.get(0), None);
400        assert_eq!(s.slot_bytes(), 0);
401    }
402
403    #[test]
404    fn what_goes_in_comes_back_out_at_the_number_it_was_given() {
405        let mut s = Slab::new();
406        let a = s.insert("a".to_string());
407        let b = s.insert("b".to_string());
408        let c = s.insert("c".to_string());
409        assert_eq!((a, b, c), (0, 1, 2), "the first three go at the end");
410        assert_eq!(s.get(a).map(String::as_str), Some("a"));
411        assert_eq!(s.get(b).map(String::as_str), Some("b"));
412        assert_eq!(s.get(c).map(String::as_str), Some("c"));
413        assert_eq!(s.len(), 3);
414    }
415
416    #[test]
417    fn a_value_can_be_changed_where_it_lies() {
418        let mut s = Slab::new();
419        let a = s.insert(vec![1u8]);
420        s.get_mut(a).expect("filled").push(2);
421        assert_eq!(s.get(a), Some(&vec![1, 2]));
422        assert_eq!(s.get_mut(9), None, "past the end");
423    }
424
425    #[test]
426    fn removing_hands_the_value_back_and_the_others_keep_their_numbers() {
427        let mut s = Slab::new();
428        let a = s.insert("a".to_string());
429        let b = s.insert("b".to_string());
430        let c = s.insert("c".to_string());
431
432        assert_eq!(s.remove(b), Some("b".to_string()));
433        assert_eq!(s.len(), 2);
434        assert_eq!(s.get(b), None);
435        assert_eq!(
436            s.get(a).map(String::as_str),
437            Some("a"),
438            "a did not move when b left"
439        );
440        assert_eq!(s.get(c).map(String::as_str), Some("c"));
441    }
442
443    #[test]
444    fn a_freed_slot_is_the_next_one_used() {
445        let mut s = Slab::new();
446        s.insert(0);
447        let b = s.insert(1);
448        s.insert(2);
449
450        s.remove(b);
451        let next = s.insert(9);
452        assert_eq!(next, b, "the hole was filled rather than the vector grown");
453        assert_eq!(s.len(), 3);
454        assert_eq!(s.get(b), Some(&9));
455    }
456
457    #[test]
458    fn the_free_list_gives_the_holes_back_in_reverse() {
459        // Not a promise to callers, but it is the shape a list threaded head
460        // first has, and a test that walks it is how a broken link shows up.
461        let mut s = Slab::new();
462        let n: Vec<u32> = (0..5).map(|i| s.insert(i)).collect();
463        for i in [1, 3, 4] {
464            s.remove(n[i]);
465        }
466        assert_eq!(s.len(), 2);
467
468        assert_eq!(s.insert(50), 4);
469        assert_eq!(s.insert(51), 3);
470        assert_eq!(s.insert(52), 1);
471        assert_eq!(s.len(), 5);
472
473        // And once the holes run out it grows again.
474        assert_eq!(s.insert(53), 5);
475        assert_eq!(s.get(0), Some(&0), "the untouched ones are untouched");
476        assert_eq!(s.get(2), Some(&2));
477    }
478
479    #[test]
480    fn freeing_twice_is_inert_rather_than_a_loop_in_the_list() {
481        // The failure this guards against is not the second remove. It is the
482        // insert after it: a free list with a slot on it twice hands the same
483        // number to two live values, and the second one silently overwrites the
484        // first. So the test is that the numbers after a double free are still
485        // all different.
486        let mut s = Slab::new();
487        let a = s.insert("a".to_string());
488        let b = s.insert("b".to_string());
489
490        assert_eq!(s.remove(a), Some("a".to_string()));
491        assert_eq!(s.remove(a), None, "already free");
492        assert_eq!(s.remove(a), None, "still already free");
493        assert_eq!(s.len(), 1);
494
495        let x = s.insert("x".to_string());
496        let y = s.insert("y".to_string());
497        let z = s.insert("z".to_string());
498        assert_eq!(x, a, "the one real hole came back");
499        assert_ne!(y, x);
500        assert_ne!(z, x);
501        assert_ne!(z, y);
502        assert_eq!(s.len(), 4);
503        assert_eq!(
504            s.get(b).map(String::as_str),
505            Some("b"),
506            "b was never touched"
507        );
508    }
509
510    #[test]
511    fn removing_something_that_was_never_there_answers_nothing() {
512        let mut s: Slab<u8> = Slab::new();
513        assert_eq!(s.remove(0), None);
514        assert_eq!(s.remove(7), None);
515        assert_eq!(s.len(), 0);
516        assert_eq!(s.insert(1), 0, "and it did not corrupt the free list");
517    }
518
519    #[test]
520    fn iterating_sees_the_values_and_not_the_holes() {
521        let mut s = Slab::new();
522        let n: Vec<u32> = (0..6).map(|i| s.insert(i * 10)).collect();
523        s.remove(n[0]);
524        s.remove(n[3]);
525        s.remove(n[5]);
526
527        let mut got: Vec<i32> = s.iter().copied().collect();
528        got.sort_unstable();
529        assert_eq!(got, [10, 20, 40]);
530        assert_eq!(got.len(), s.len());
531    }
532
533    #[test]
534    fn clearing_hands_the_memory_back_and_starts_the_numbers_again() {
535        let mut s = Slab::with_capacity(64);
536        for i in 0..64 {
537            s.insert(i);
538        }
539        assert!(s.slot_bytes() >= 64 * mem::size_of::<Slot<i32>>());
540
541        s.clear();
542        assert_eq!(s.len(), 0);
543        assert!(s.is_empty());
544        assert_eq!(s.slot_bytes(), 0, "the vector went, not just the values");
545        assert_eq!(s.get(0), None);
546        assert_eq!(s.insert(1), 0, "numbering starts over");
547    }
548
549    #[test]
550    fn the_running_total_says_what_the_walk_says_whatever_was_done_to_it() {
551        // The only property that matters. Everything else in the tracking is an
552        // implementation of it, so the test is a long run of every operation
553        // there is with the two numbers checked against each other after each
554        // one. A missed bookkeeping step in insert, get_mut or remove shows up
555        // here as a difference and nowhere else.
556        let mut s: Slab<String> = Slab::new();
557        s.track_bytes(true);
558        let mut live: Vec<u32> = Vec::new();
559        let mut n = 0usize;
560        for step in 0..500 {
561            match step % 5 {
562                0 | 1 => {
563                    n += 1;
564                    live.push(s.insert("x".repeat(n % 17)));
565                }
566                2 | 3 => {
567                    if let Some(&at) = live.get(step % live.len().max(1)) {
568                        s.get_mut(at).expect("filled").push('y');
569                    }
570                }
571                _ => {
572                    if !live.is_empty() {
573                        let at = live.swap_remove(step % live.len());
574                        s.remove(at);
575                    }
576                }
577            }
578            assert_eq!(
579                s.settled_bytes(),
580                s.value_bytes(),
581                "after step {step}, which was a {}",
582                step % 5
583            );
584        }
585        assert!(n > 0 && !live.is_empty(), "the run did something");
586    }
587
588    #[test]
589    fn a_slot_written_over_and_over_is_only_asked_once_before_a_reading() {
590        // What makes the total affordable. Sixty four writes to the same key in
591        // a batch put it on the list once, so the reading at the end of the
592        // batch asks it once, and the reading is still right.
593        let mut s: Slab<String> = Slab::new();
594        s.track_bytes(true);
595        let a = s.insert(String::new());
596        let b = s.insert("bb".to_string());
597        assert_eq!(s.settled_bytes(), 2);
598
599        for _ in 0..64 {
600            s.get_mut(a).expect("filled").push('a');
601        }
602        assert_eq!(s.soiled.len(), 1, "one slot written down, not sixty four");
603        assert_eq!(s.settled_bytes(), 66);
604        assert_eq!(s.soiled.len(), 0, "and the list is empty again");
605        assert_eq!(s.get(b).map(String::as_str), Some("bb"));
606    }
607
608    #[test]
609    fn nothing_is_counted_until_the_total_is_switched_on() {
610        // Off, the slab still answers the question, it just walks for it. The
611        // first switch on is the walk the total starts from, and switching it on
612        // again when it is already on does not walk a second time.
613        let mut s: Slab<String> = Slab::new();
614        s.insert("abc".to_string());
615        s.insert("de".to_string());
616        assert_eq!(s.settled_bytes(), 5, "the walk, because nothing is tracked");
617        assert_eq!(s.clean, 0, "and it did not start a total behind our back");
618
619        s.track_bytes(true);
620        assert_eq!(s.clean, 5, "the walk it starts from");
621        s.track_bytes(true);
622        assert_eq!(s.clean, 5);
623
624        s.insert("fghi".to_string());
625        assert_eq!(s.settled_bytes(), 9);
626
627        s.track_bytes(false);
628        assert_eq!(s.clean, 0, "and it gave the bookkeeping back");
629        assert_eq!(s.settled_bytes(), 9, "walking again for the same answer");
630    }
631
632    #[test]
633    fn clearing_takes_the_total_with_it() {
634        // FLUSHALL. A total left behind after the values went would be a server
635        // that thinks it is holding an empty database's worth of sets.
636        let mut s: Slab<String> = Slab::new();
637        s.track_bytes(true);
638        for i in 0..10 {
639            s.insert("z".repeat(i));
640        }
641        assert_eq!(s.settled_bytes(), 45);
642
643        s.clear();
644        assert_eq!(s.settled_bytes(), 0);
645        assert_eq!(s.slot_bytes(), 0);
646
647        s.insert("new".to_string());
648        assert_eq!(s.settled_bytes(), 3, "and it counts again from there");
649    }
650
651    #[test]
652    fn a_reused_slot_is_counted_as_what_is_in_it_now() {
653        // The awkward one. A slot goes on the list when its value is removed and
654        // comes back filled with something else before anybody reads the total,
655        // so the reading has to ask the new value and not remember the old.
656        let mut s: Slab<String> = Slab::new();
657        s.track_bytes(true);
658        let a = s.insert("aaaaa".to_string());
659        assert_eq!(s.settled_bytes(), 5);
660
661        s.remove(a);
662        let b = s.insert("bb".to_string());
663        assert_eq!(b, a, "the same slot came back");
664        assert_eq!(s.settled_bytes(), 2);
665        assert_eq!(s.value_bytes(), 2);
666    }
667
668    #[test]
669    fn a_churning_workload_does_not_grow_the_vector() {
670        // The reason the free list exists. Ten thousand keys created and deleted
671        // one after another is a shape a real server sees, and without reuse it
672        // would leave ten thousand dead slots behind.
673        let mut s = Slab::with_capacity(4);
674        let before = s.slot_bytes();
675        for i in 0..10_000 {
676            let at = s.insert(i);
677            assert_eq!(at, 0, "the same slot every time");
678            assert_eq!(s.remove(at), Some(i));
679        }
680        assert_eq!(s.len(), 0);
681        assert_eq!(s.slot_bytes(), before);
682    }
683}