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}