Skip to main content

yo_kv/
blob.rs

1//! Variable length bytes belonging to one collection, back to back.
2//!
3//! Two things inside a collection are the same problem. A set or a hash interns
4//! its member and field names so that writing the same field again touches no
5//! name bytes (`05` section 3), and a hash in the native band has to put its
6//! values somewhere that is not one allocation per field, because one allocation
7//! per field is the thing the element per row layout exists to avoid. Both want
8//! a stretch of bytes with an offset handed back, both want a rewrite to leave
9//! the old bytes behind rather than move everything after them, and both want
10//! those bytes back eventually.
11//!
12//! ```text
13//!   bytes                                    dead
14//! +-------+---------+-------+---------+     bytes nothing points at, counted
15//! | name  | oldval  | name  | value   |     here and given back once they
16//! +-------+---------+-------+---------+     outnumber the ones that are live
17//!    ^ at, len              ^ at, len
18//! ```
19//!
20//! # It does not hold the references
21//!
22//! [`Blob::push`] hands back an offset and nothing else, and [`Blob::read`] takes
23//! an offset and a length. The reference is the caller's to shape, which is not
24//! ceremony, because the two callers want different shapes and neither of them
25//! is a compromise with the other.
26//!
27//! [`crate::Elements`] has eight spare bits in a row it is trying to keep at
28//! eight bytes, so it keeps a name's length in those and its reference is the
29//! offset on its own. A hash value has no spare bits anywhere and cannot be
30//! capped at two hundred and fifty five bytes either, because Redis lets one be
31//! half a gigabyte, so [`Blob::push_sized`] writes the length into the blob in
32//! front of the bytes and its reference is also the offset on its own. One byte
33//! of prefix for a value under two hundred and fifty five bytes and five for
34//! anything longer, which is a byte per field against the four a length beside
35//! the offset would cost.
36//!
37//! [`Span`] is here for the callers that have no reason to pack it tighter.
38//!
39//! # Giving the dead bytes back
40//!
41//! A rewrite appends and abandons, so a hash whose values are written over and
42//! over holds every value it ever had until something clears up. That something
43//! is [`Blob::compact`], which the owner runs when [`Blob::worth_compacting`]
44//! says so and drives itself, because the owner is the only thing that knows
45//! where its references are. Until then the dead bytes are counted and reported
46//! rather than pretended away, which is the rule the arena follows and for the
47//! same reason: a number `INFO memory` can show is a leak you can see.
48//!
49//! Half is the line, with a floor of four kilobytes under it. Below the half the
50//! copy costs more than the bytes are worth, and below the floor there are not
51//! enough bytes to be worth a copy at any ratio at all.
52
53/// Where something is in a blob, for a caller with no reason to pack it tighter.
54///
55/// Eight bytes. A hash value uses this, because a value can be as long as the
56/// 512 MiB Redis puts on everything and there is no shorter length that holds
57/// it.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
59pub struct Span {
60    /// Where the bytes start.
61    pub at: u32,
62    /// How many of them there are.
63    pub len: u32,
64}
65
66/// Dead bytes below this are left alone whatever the ratio says.
67const FLOOR: usize = 4096;
68
69/// The shortest run whose length goes in front of it in five bytes, not one.
70const LONG: usize = 255;
71
72/// How many bytes a long run's length prefix takes, the marker included.
73const LONG_PREFIX: usize = 5;
74
75/// How far past `at` the bytes start, and how many of them there are.
76///
77/// The prefix is one byte holding the length, or the marker followed by the
78/// length in four. Two hundred and fifty five is the marker rather than a
79/// length, so a run of exactly that many bytes takes the long form and pays four
80/// bytes it did not have to. That is one length out of the whole range and it
81/// buys a check that is a compare against a constant.
82#[inline]
83fn sized_head(bytes: &[u8], at: usize) -> (usize, usize) {
84    let head = usize::from(bytes[at]);
85    if head < LONG {
86        return (1, head);
87    }
88    let head: [u8; 4] = bytes[at + 1..at + LONG_PREFIX]
89        .try_into()
90        .expect("four bytes of length behind the marker");
91    (LONG_PREFIX, u32::from_le_bytes(head) as usize)
92}
93
94/// Bytes belonging to one collection, appended to and occasionally rebuilt.
95#[derive(Debug, Default, Clone)]
96pub struct Blob {
97    bytes: Vec<u8>,
98    dead: usize,
99}
100
101impl Blob {
102    /// An empty blob that has not allocated anything.
103    ///
104    /// A collection is made by its first write, so the empty case is the common
105    /// one and it does not deserve an allocation.
106    #[must_use]
107    pub const fn new() -> Blob {
108        Blob {
109            bytes: Vec::new(),
110            dead: 0,
111        }
112    }
113
114    /// An empty blob with room already taken.
115    #[must_use]
116    pub fn with_capacity(n: usize) -> Blob {
117        Blob {
118            bytes: Vec::with_capacity(n),
119            dead: 0,
120        }
121    }
122
123    /// Every byte here, live and dead together.
124    #[inline]
125    #[must_use]
126    pub const fn len(&self) -> usize {
127        self.bytes.len()
128    }
129
130    /// Whether nothing has ever been written.
131    #[inline]
132    #[must_use]
133    pub const fn is_empty(&self) -> bool {
134        self.bytes.is_empty()
135    }
136
137    /// Bytes nothing points at any more.
138    #[inline]
139    #[must_use]
140    pub const fn dead(&self) -> usize {
141        self.dead
142    }
143
144    /// What this costs, which is the allocation and not the used part of it.
145    #[inline]
146    #[must_use]
147    pub fn memory_bytes(&self) -> usize {
148        self.bytes.capacity()
149    }
150
151    /// Append `bytes` and say where they went.
152    ///
153    /// # Panics
154    ///
155    /// If the blob would pass four gigabytes, which no collection reaches
156    /// without passing a row limit first.
157    #[inline]
158    pub fn push(&mut self, bytes: &[u8]) -> u32 {
159        let at = u32::try_from(self.bytes.len()).expect("the blob is under 4 GiB");
160        // By [`crate::grow`]'s policy and not by `Vec`'s, for the same reason
161        // the row array above it grows that way: a blob holding the names of a
162        // large collection is megabytes, and half of a doubled one is air.
163        crate::grow::reserve(&mut self.bytes, bytes.len());
164        self.bytes.extend_from_slice(bytes);
165        at
166    }
167
168    /// Append `bytes` and say where they went, as a [`Span`].
169    ///
170    /// # Panics
171    ///
172    /// If the blob would pass four gigabytes, or `bytes` is longer than one.
173    #[inline]
174    pub fn push_span(&mut self, bytes: &[u8]) -> Span {
175        Span {
176            at: self.push(bytes),
177            len: u32::try_from(bytes.len()).expect("no one value is 4 GiB"),
178        }
179    }
180
181    /// Append `bytes` behind their own length and say where the length went.
182    ///
183    /// For the caller that has nowhere else to keep a length. The offset alone
184    /// is the whole reference, which is four bytes rather than the eight a
185    /// [`Span`] costs, against one byte in the blob for anything under two
186    /// hundred and fifty five and five for anything longer.
187    ///
188    /// # Panics
189    ///
190    /// If the blob would pass four gigabytes, or `bytes` is longer than one.
191    pub fn push_sized(&mut self, bytes: &[u8]) -> u32 {
192        if bytes.len() < LONG {
193            let head = [u8::try_from(bytes.len()).expect("under LONG")];
194            let at = self.push(&head);
195            self.push(bytes);
196            return at;
197        }
198        let len = u32::try_from(bytes.len()).expect("no one value is 4 GiB");
199        let mut head = [0u8; LONG_PREFIX];
200        head[0] = u8::try_from(LONG).expect("LONG is one byte");
201        head[1..].copy_from_slice(&len.to_le_bytes());
202        let at = self.push(&head);
203        self.push(bytes);
204        at
205    }
206
207    /// The bytes a [`Blob::push_sized`] offset points at.
208    ///
209    /// # Panics
210    ///
211    /// If `at` is not the start of a run that was pushed with its length.
212    #[inline]
213    #[must_use]
214    pub fn sized(&self, at: u32) -> &[u8] {
215        let at = at as usize;
216        let (skip, len) = sized_head(&self.bytes, at);
217        &self.bytes[at + skip..at + skip + len]
218    }
219
220    /// How long a [`Blob::push_sized`] run is, without reading it.
221    ///
222    /// This is what `HSTRLEN` asks. It used to be free, because the length was
223    /// in the reference, and now it is one byte off the front of the value. That
224    /// byte is on the same cache line as the value itself, so the answer costs
225    /// the miss the caller would have taken to read the value anyway.
226    #[inline]
227    #[must_use]
228    pub fn sized_len(&self, at: u32) -> usize {
229        sized_head(&self.bytes, at as usize).1
230    }
231
232    /// Say that a [`Blob::push_sized`] run is not pointed at any more.
233    #[inline]
234    pub fn release_sized(&mut self, at: u32) {
235        let (skip, len) = sized_head(&self.bytes, at as usize);
236        self.release(skip + len);
237    }
238
239    /// The `len` bytes at `at`.
240    ///
241    /// # Panics
242    ///
243    /// If they are not inside the blob, which means a reference was kept across
244    /// a [`Blob::compact`] without being moved.
245    #[inline]
246    #[must_use]
247    pub fn read(&self, at: u32, len: usize) -> &[u8] {
248        let at = at as usize;
249        &self.bytes[at..at + len]
250    }
251
252    /// The bytes a [`Span`] points at.
253    #[inline]
254    #[must_use]
255    pub fn span(&self, span: Span) -> &[u8] {
256        self.read(span.at, span.len as usize)
257    }
258
259    /// Say that `len` bytes are not pointed at any more.
260    ///
261    /// This frees nothing. It moves the number that decides when
262    /// [`Blob::compact`] is worth running.
263    #[inline]
264    pub const fn release(&mut self, len: usize) {
265        self.dead += len;
266    }
267
268    /// Say that a [`Span`] is not pointed at any more.
269    #[inline]
270    pub const fn release_span(&mut self, span: Span) {
271        self.release(span.len as usize);
272    }
273
274    /// Throw everything away and keep the allocation.
275    #[inline]
276    pub fn clear(&mut self) {
277        self.bytes.clear();
278        self.dead = 0;
279    }
280
281    /// Whether the dead bytes are worth a rebuild.
282    #[inline]
283    #[must_use]
284    pub const fn worth_compacting(&self) -> bool {
285        self.dead >= FLOOR && self.dead * 2 >= self.bytes.len()
286    }
287
288    /// Rebuild, keeping only what `keep` points at.
289    ///
290    /// The owner walks its own references and calls [`Keep::moved`] on each,
291    /// which copies those bytes into the new blob and rewrites the offset in
292    /// place. Anything not offered is gone. A reference the owner forgets to
293    /// offer becomes a reference into a blob that moved underneath it, and
294    /// [`Blob::read`] turns that into a panic rather than into wrong bytes.
295    ///
296    /// The order the owner walks in becomes the order in the new blob, so
297    /// walking in row order leaves a sequential read sequential.
298    pub fn compact<F>(&mut self, keep: F)
299    where
300        F: FnOnce(&mut Keep<'_>),
301    {
302        let fresh = {
303            let mut k = Keep {
304                old: &self.bytes,
305                fresh: Vec::with_capacity(self.bytes.len() - self.dead),
306            };
307            keep(&mut k);
308            k.fresh
309        };
310        self.bytes = fresh;
311        self.dead = 0;
312    }
313}
314
315/// A rebuild in progress, handed to the owner so it can move its references.
316#[derive(Debug)]
317pub struct Keep<'a> {
318    old: &'a [u8],
319    fresh: Vec<u8>,
320}
321
322impl Keep<'_> {
323    /// Carry the `len` bytes at `*at` over, and point `at` at where they landed.
324    ///
325    /// # Panics
326    ///
327    /// If they are not inside the old blob, which is the mistake
328    /// [`Blob::read`] catches and it is caught here for the same reason.
329    #[inline]
330    pub fn moved(&mut self, at: &mut u32, len: usize) {
331        let from = *at as usize;
332        let to = u32::try_from(self.fresh.len()).expect("the blob only shrinks here");
333        self.fresh.extend_from_slice(&self.old[from..from + len]);
334        *at = to;
335    }
336
337    /// The same for a [`Span`], whose length does not change.
338    #[inline]
339    pub fn moved_span(&mut self, span: &mut Span) {
340        let len = span.len as usize;
341        self.moved(&mut span.at, len);
342    }
343
344    /// The same for a [`Blob::push_sized`] run, prefix and all.
345    ///
346    /// The length is in the bytes being moved rather than in the reference, so
347    /// it is read off the old copy, which is the whole reason [`Keep::peek`] is
348    /// here.
349    #[inline]
350    pub fn moved_sized(&mut self, at: &mut u32) {
351        let (skip, len) = sized_head(self.old, *at as usize);
352        self.moved(at, skip + len);
353    }
354
355    /// The `len` bytes at `at`, as they were before the rebuild started.
356    ///
357    /// A reference whose length is written into the bytes rather than held
358    /// beside them has to read those bytes to know how many to carry over, and
359    /// the blob it would normally read from is half moved by the time it is
360    /// asked. This is the old copy, which is still whole.
361    ///
362    /// # Panics
363    ///
364    /// If they are not inside the old blob.
365    #[inline]
366    #[must_use]
367    pub fn peek(&self, at: u32, len: usize) -> &[u8] {
368        let at = at as usize;
369        &self.old[at..at + len]
370    }
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    #[test]
378    fn what_goes_in_comes_back_out() {
379        let mut b = Blob::new();
380        let one = b.push_span(b"field");
381        let two = b.push_span(b"");
382        let three = b.push_span(b"a longer value than the first one");
383
384        assert_eq!(b.span(one), b"field");
385        assert_eq!(b.span(two), b"");
386        assert_eq!(b.span(three), b"a longer value than the first one");
387        assert_eq!(b.len(), 5 + 33);
388        assert_eq!(b.dead(), 0);
389    }
390
391    #[test]
392    fn a_length_written_in_front_reads_back_at_every_length() {
393        let mut b = Blob::new();
394        let lens = [0usize, 1, 2, 100, 253, 254, 255, 256, 257, 70_000];
395        let at: Vec<u32> = lens
396            .iter()
397            .enumerate()
398            .map(|(i, &n)| {
399                let byte = u8::try_from(i).expect("ten of them");
400                b.push_sized(&vec![byte; n])
401            })
402            .collect();
403
404        for (i, (&n, &at)) in lens.iter().zip(&at).enumerate() {
405            let byte = u8::try_from(i).expect("ten of them");
406            assert_eq!(b.sized_len(at), n, "the length came back wrong");
407            assert_eq!(b.sized(at), &vec![byte; n][..], "the bytes came back wrong");
408        }
409
410        // One byte of prefix under the marker and five at it and above.
411        let short: usize = lens.iter().filter(|&&n| n < 255).map(|&n| n + 1).sum();
412        let long: usize = lens.iter().filter(|&&n| n >= 255).map(|&n| n + 5).sum();
413        assert_eq!(b.len(), short + long);
414    }
415
416    #[test]
417    fn a_run_that_carries_its_own_length_moves_with_it() {
418        let mut b = Blob::new();
419        // Long and short mixed, so the rebuild has to read both prefix forms off
420        // the copy it is reading from rather than the one it is writing.
421        let mut live: Vec<u32> = Vec::new();
422        for i in 0..100u32 {
423            let n = if i % 3 == 0 { 300 } else { 40 };
424            let byte = u8::try_from(i % 251).expect("under 251");
425            let first = b.push_sized(&vec![byte; n]);
426            b.release_sized(first);
427            live.push(b.push_sized(&vec![byte; n]));
428        }
429        assert!(b.worth_compacting());
430        let before = b.len();
431
432        b.compact(|k| {
433            for at in &mut live {
434                k.moved_sized(at);
435            }
436        });
437
438        assert_eq!(b.dead(), 0);
439        assert_eq!(b.len() * 2, before, "the dead half went and no more");
440        for (i, &at) in live.iter().enumerate() {
441            let i = u32::try_from(i).expect("a hundred of them");
442            let n = if i % 3 == 0 { 300 } else { 40 };
443            let byte = u8::try_from(i % 251).expect("under 251");
444            assert_eq!(b.sized(at), &vec![byte; n][..], "a reference moved wrongly");
445        }
446    }
447
448    #[test]
449    fn a_rewrite_leaves_the_old_bytes_behind_and_says_so() {
450        let mut b = Blob::new();
451        let old = b.push_span(b"before");
452        b.release_span(old);
453        let new = b.push_span(b"after");
454
455        assert_eq!(b.span(new), b"after");
456        assert_eq!(b.dead(), 6, "the old bytes are still there and counted");
457        assert_eq!(b.len(), 11);
458    }
459
460    #[test]
461    fn the_dead_bytes_come_back_and_the_live_ones_move() {
462        let mut b = Blob::new();
463        // Twenty kilobytes written, half of it abandoned, which is over the
464        // floor and at the ratio.
465        let mut live: Vec<Span> = Vec::new();
466        for i in 0..100u32 {
467            let bytes = vec![b'a' + u8::try_from(i % 26).expect("under 26"); 100];
468            let first = b.push_span(&bytes);
469            b.release_span(first);
470            live.push(b.push_span(&bytes));
471        }
472        assert_eq!(b.dead(), 10_000);
473        assert!(b.worth_compacting());
474
475        let want: Vec<Vec<u8>> = live.iter().map(|&s| b.span(s).to_vec()).collect();
476        b.compact(|k| {
477            for span in &mut live {
478                k.moved_span(span);
479            }
480        });
481
482        assert_eq!(b.dead(), 0);
483        assert_eq!(b.len(), 10_000, "only the live half survived");
484        for (span, bytes) in live.iter().zip(&want) {
485            assert_eq!(b.span(*span), &bytes[..], "a reference moved wrongly");
486        }
487    }
488
489    #[test]
490    fn a_small_or_mostly_live_blob_is_left_alone() {
491        let mut b = Blob::new();
492        b.push(&vec![0u8; 100_000]);
493        b.release(3000);
494        assert!(!b.worth_compacting(), "under the floor, whatever the ratio");
495
496        let mut c = Blob::new();
497        c.push(&vec![0u8; 100_000]);
498        c.release(40_000);
499        assert!(!c.worth_compacting(), "over the floor and under the half");
500        c.release(10_000);
501        assert!(c.worth_compacting(), "and at the half it is worth doing");
502    }
503
504    #[test]
505    fn clearing_keeps_the_allocation_and_forgets_the_dead() {
506        let mut b = Blob::with_capacity(1024);
507        b.push(b"something");
508        b.release(4);
509        b.clear();
510
511        assert!(b.is_empty());
512        assert_eq!(b.dead(), 0);
513        assert!(b.memory_bytes() >= 1024, "the allocation stayed");
514    }
515}