1use std::collections::VecDeque;
17use std::rc::Rc;
18
19use timely::Accountable;
20use timely::container::{PushInto, SizableContainer};
21use timely::progress::{Antichain, Timestamp};
22use timely::progress::frontier::AntichainRef;
23
24use crate::consolidation::Consolidate;
25use crate::difference::Semigroup;
26use crate::lattice::Lattice;
27
28use super::Chunk;
29
30const TARGET: usize = 8192;
32
33pub struct VecChunk<K, V, T, R>(Rc<Vec<((K, V), T, R)>>);
35
36impl<K, V, T, R> VecChunk<K, V, T, R> {
37 pub fn as_slice(&self) -> &[((K, V), T, R)] { &self.0 }
39}
40
41impl<K, V, T, R> Clone for VecChunk<K, V, T, R> {
42 fn clone(&self) -> Self { VecChunk(Rc::clone(&self.0)) }
43}
44impl<K, V, T, R> Default for VecChunk<K, V, T, R> {
45 fn default() -> Self { VecChunk(Rc::new(Vec::new())) }
46}
47
48pub type ChunkSpine<K, V, T, R> = super::ChunkSpine<VecChunk<K, V, T, R>>;
50pub type ChunkBatcher<K, V, T, R> = super::ChunkBatcher<crate::batcher::merge::chunker::ContainerChunker<VecChunk<K, V, T, R>>, VecChunk<K, V, T, R>>;
53pub type ChunkBuilder<K, V, T, R> = super::ChunkBuilder<VecChunk<K, V, T, R>>;
55
56impl<K: 'static, V: 'static, T: 'static, R: 'static> Accountable for VecChunk<K, V, T, R> {
57 fn record_count(&self) -> i64 { self.0.len() as i64 }
58}
59
60impl<K, V, T, R> SizableContainer for VecChunk<K, V, T, R>
61where K: Clone+'static, V: Clone+'static, T: Clone+'static, R: Clone+'static {
62 fn at_capacity(&self) -> bool { self.0.len() >= TARGET }
65 fn ensure_capacity(&mut self, _stash: &mut Option<Self>) {
66 let inner = Rc::make_mut(&mut self.0);
67 inner.reserve(TARGET.saturating_sub(inner.len()));
68 }
69}
70
71impl<K, V, T, R> Consolidate for VecChunk<K, V, T, R>
72where K: Ord+Clone+'static, V: Ord+Clone+'static, T: Ord+Clone+'static, R: Semigroup+'static {
73 fn len(&self) -> usize { self.0.len() }
74 fn clear(&mut self) { Rc::make_mut(&mut self.0).clear() }
75 fn consolidate_into(&mut self, target: &mut Self) {
76 Rc::make_mut(&mut self.0).consolidate_into(Rc::make_mut(&mut target.0));
77 }
78}
79
80impl<K, V, T, R> PushInto<((K, V), T, R)> for VecChunk<K, V, T, R>
81where K: Clone+'static, V: Clone+'static, T: Clone+'static, R: Clone+'static {
82 fn push_into(&mut self, item: ((K, V), T, R)) { Rc::make_mut(&mut self.0).push(item); }
83}
84
85fn take<K: Clone, V: Clone, T: Clone, R: Clone>(chunk: VecChunk<K, V, T, R>) -> Vec<((K, V), T, R)> {
87 Rc::try_unwrap(chunk.0).unwrap_or_else(|rc| (*rc).clone())
88}
89
90impl<K, V, T, R> Chunk for VecChunk<K, V, T, R>
91where K: Ord+Clone+'static, V: Ord+Clone+'static, T: Lattice+Timestamp, R: Semigroup+'static {
92 type Time = T;
93
94 const TARGET: usize = TARGET;
95
96 fn len(&self) -> usize { self.0.len() }
97
98 fn merge(in1: &mut VecDeque<Self>, in2: &mut VecDeque<Self>, out: &mut VecDeque<Self>) {
104 fn kv<K, V, T, R>(u: &((K, V), T, R)) -> (&K, &V) { (&u.0.0, &u.0.1) }
105
106 let mut result: Vec<((K, V), T, R)> = Vec::with_capacity(TARGET);
107 let mut flush = |result: &mut Vec<((K, V), T, R)>, force: bool| {
108 if result.len() >= TARGET || (force && !result.is_empty()) {
109 out.push_back(VecChunk(Rc::new(std::mem::replace(result, Vec::with_capacity(TARGET)))));
110 }
111 };
112
113 let mut c1 = in1.pop_front().unwrap();
117 let mut c2 = in2.pop_front().unwrap();
118 let (mut p1, mut p2) = (0usize, 0usize);
119 while p1 < c1.0.len() && p2 < c2.0.len() {
120 let a = &c1.0[p1];
121 let b = &c2.0[p2];
122 match (kv(a), &a.1).cmp(&(kv(b), &b.1)) {
123 std::cmp::Ordering::Less => {
126 let run = gallop(&c1.0[..], p1 + 1, |u| (kv(u), &u.1) < (kv(b), &b.1));
127 for piece in c1.0[p1..run].chunks(TARGET) {
128 result.extend_from_slice(piece);
129 flush(&mut result, false);
130 }
131 p1 = run;
132 }
133 std::cmp::Ordering::Greater => {
134 let run = gallop(&c2.0[..], p2 + 1, |u| (kv(u), &u.1) < (kv(a), &a.1));
135 for piece in c2.0[p2..run].chunks(TARGET) {
136 result.extend_from_slice(piece);
137 flush(&mut result, false);
138 }
139 p2 = run;
140 }
141 std::cmp::Ordering::Equal => {
142 let mut diff = a.2.clone();
143 diff.plus_equals(&b.2);
144 if !diff.is_zero() {
145 result.push((a.0.clone(), a.1.clone(), diff));
146 }
147 p1 += 1;
148 p2 += 1;
149 flush(&mut result, false);
150 }
151 }
152 if p1 == c1.0.len() {
154 match in1.pop_front() { Some(c) => { c1 = c; p1 = 0; } None => break }
155 }
156 if p2 == c2.0.len() {
157 match in2.pop_front() { Some(c) => { c2 = c; p2 = 0; } None => break }
158 }
159 }
160 flush(&mut result, true);
161 if p1 < c1.0.len() { in1.push_front(VecChunk(Rc::new(c1.0[p1..].to_vec()))); }
164 if p2 < c2.0.len() { in2.push_front(VecChunk(Rc::new(c2.0[p2..].to_vec()))); }
165 }
166
167 fn extract(
168 input: &mut VecDeque<Self>,
169 frontier: AntichainRef<T>,
170 residual: &mut Antichain<T>,
171 keep: &mut VecDeque<Self>,
172 ship: &mut VecDeque<Self>,
173 ) {
174 let Some(chunk) = input.pop_front() else { return };
178 let (mut k, mut s) = (Vec::new(), Vec::new());
179 for u in take(chunk) {
180 if frontier.less_equal(&u.1) { residual.insert_ref(&u.1); k.push(u); }
181 else { s.push(u); }
182 }
183 if !k.is_empty() { keep.push_back(VecChunk(Rc::new(k))); }
184 if !s.is_empty() { ship.push_back(VecChunk(Rc::new(s))); }
185 }
186
187 fn advance(
188 input: &mut VecDeque<Self>,
189 frontier: AntichainRef<T>,
190 done: bool,
191 out: &mut VecDeque<Self>,
192 ) {
193 let mut stash: Vec<Vec<((K, V), T, R)>> = Vec::new();
197 let mut buf = match input.pop_front() { Some(chunk) => take(chunk), None => return };
201 while let Some(chunk) = input.pop_front() {
202 let mut v = take(chunk);
203 buf.append(&mut v);
204 stash.push(v);
205 }
206 if buf.is_empty() { return; }
207
208 if !done && buf[0].0 == buf[buf.len() - 1].0 {
212 input.push_front(VecChunk(Rc::new(buf)));
213 return;
214 }
215
216 let end = if done { buf.len() } else {
218 let last_kv = buf[buf.len() - 1].0.clone();
219 let mut start = buf.len();
220 while start > 0 && buf[start - 1].0 == last_kv { start -= 1; }
221 start
222 };
223 if end < buf.len() {
224 input.push_front(VecChunk(Rc::new(buf.split_off(end))));
225 }
226 let mut result = stash.pop().unwrap_or_default();
229 let mut i = 0;
230 while i < buf.len() {
231 let mut j = i;
232 while j < buf.len() && buf[j].0 == buf[i].0 { j += 1; }
233 for u in &mut buf[i..j] { u.1.advance_by(frontier); }
234 buf[i..j].sort_by(|a, b| a.1.cmp(&b.1));
236 let mut k = i;
237 while k < j {
238 let kv = buf[k].0.clone();
239 let t = buf[k].1.clone();
240 let mut diff = buf[k].2.clone();
241 k += 1;
242 while k < j && buf[k].1 == t { diff.plus_equals(&buf[k].2); k += 1; }
243 if !diff.is_zero() {
244 result.push((kv, t, diff));
245 if result.len() >= TARGET { out.push_back(VecChunk(Rc::new(std::mem::replace(&mut result, stash.pop().unwrap_or_default())))); }
246 }
247 }
248 i = j;
249 }
250 if !result.is_empty() { out.push_back(VecChunk(Rc::new(result))); }
251 }
252
253 fn settle(input: &mut VecDeque<Self>, done: bool, out: &mut VecDeque<Self>) {
257 super::pack(
258 input, done, out,
259 |run| VecChunk(Rc::new(run.into_iter().flat_map(take).collect())),
260 |chunk, n| { let mut rows = take(chunk); let rest = rows.split_off(n); (VecChunk(Rc::new(rows)), VecChunk(Rc::new(rest))) },
261 |chunk| chunk,
262 );
263 }
264}
265
266fn gallop<U>(s: &[U], start: usize, pred: impl Fn(&U) -> bool) -> usize {
270 let mut pos = start;
271 if pos < s.len() && pred(&s[pos]) {
272 let mut step = 1;
273 while pos + step < s.len() && pred(&s[pos + step]) { pos += step; step <<= 1; }
274 step >>= 1;
275 while step > 0 {
276 if pos + step < s.len() && pred(&s[pos + step]) { pos += step; }
277 step >>= 1;
278 }
279 pos += 1;
280 }
281 pos
282}
283
284pub mod cursor {
286
287 use std::marker::PhantomData;
288
289 use timely::progress::Timestamp;
290
291 use crate::difference::Semigroup;
292 use crate::lattice::Lattice;
293 use crate::trace::Navigable;
294 use crate::trace::cursor::Cursor;
295 use crate::trace::implementations::{BatchContainer, Layout, Vector};
296
297 use crate::trace::chunk::vec::VecChunk;
298
299 use super::gallop;
300
301 pub struct VecChunkCursor<K, V, T, R> {
304 key_pos: usize,
305 val_pos: usize,
306 phantom: PhantomData<(K, V, T, R)>,
307 }
308
309 impl<K, V, T, R> Cursor for VecChunkCursor<K, V, T, R>
310 where K: Ord+Clone+'static, V: Ord+Clone+'static, T: Lattice+Timestamp, R: Ord+Semigroup+'static {
311 type Storage = VecChunk<K, V, T, R>;
312
313 type KeyContainer = <Vector<((K, V), T, R)> as Layout>::KeyContainer;
314 type Key<'a> = <<Vector<((K, V), T, R)> as Layout>::KeyContainer as BatchContainer>::ReadItem<'a>;
315 type ValContainer = <Vector<((K, V), T, R)> as Layout>::ValContainer;
316 type Val<'a> = <<Vector<((K, V), T, R)> as Layout>::ValContainer as BatchContainer>::ReadItem<'a>;
317 type ValOwn = <<Vector<((K, V), T, R)> as Layout>::ValContainer as BatchContainer>::Owned;
318 type TimeContainer = <Vector<((K, V), T, R)> as Layout>::TimeContainer;
319 type TimeGat<'a> = <<Vector<((K, V), T, R)> as Layout>::TimeContainer as BatchContainer>::ReadItem<'a>;
320 type Time = <<Vector<((K, V), T, R)> as Layout>::TimeContainer as BatchContainer>::Owned;
321 type DiffContainer = <Vector<((K, V), T, R)> as Layout>::DiffContainer;
322 type DiffGat<'a> = <<Vector<((K, V), T, R)> as Layout>::DiffContainer as BatchContainer>::ReadItem<'a>;
323 type Diff = <<Vector<((K, V), T, R)> as Layout>::DiffContainer as BatchContainer>::Owned;
324
325 fn key_valid(&self, s: &Self::Storage) -> bool { self.key_pos < s.0.len() }
326 fn val_valid(&self, s: &Self::Storage) -> bool {
327 self.key_pos < s.0.len() && self.val_pos < s.0.len() && s.0[self.val_pos].0.0 == s.0[self.key_pos].0.0
328 }
329 fn key<'a>(&self, s: &'a Self::Storage) -> &'a K { &s.0[self.key_pos].0.0 }
330 fn val<'a>(&self, s: &'a Self::Storage) -> &'a V { &s.0[self.val_pos].0.1 }
331 fn get_key<'a>(&self, s: &'a Self::Storage) -> Option<&'a K> {
332 if self.key_valid(s) { Some(self.key(s)) } else { None }
333 }
334 fn get_val<'a>(&self, s: &'a Self::Storage) -> Option<&'a V> {
335 if self.val_valid(s) { Some(self.val(s)) } else { None }
336 }
337 fn map_times<L: FnMut(&T, &R)>(&mut self, s: &Self::Storage, mut logic: L) {
338 if !self.val_valid(s) { return; }
339 let kv = &s.0[self.val_pos].0;
340 let mut i = self.val_pos;
341 while i < s.0.len() && &s.0[i].0 == kv {
342 logic(&s.0[i].1, &s.0[i].2);
343 i += 1;
344 }
345 }
346 fn step_key(&mut self, s: &Self::Storage) {
347 if self.key_pos >= s.0.len() { return; }
350 let key = s.0[self.key_pos].0.0.clone();
351 let mut i = self.key_pos;
352 while i < s.0.len() && s.0[i].0.0 == key { i += 1; }
353 self.key_pos = i;
354 self.val_pos = i;
355 }
356 fn seek_key(&mut self, s: &Self::Storage, key: &K) {
357 self.key_pos = gallop(&s.0, self.key_pos, |u| &u.0.0 < key);
359 self.val_pos = self.key_pos;
360 }
361 fn step_val(&mut self, s: &Self::Storage) {
362 if !self.val_valid(s) { return; }
363 let kv = s.0[self.val_pos].0.clone();
364 let mut i = self.val_pos;
365 while i < s.0.len() && s.0[i].0 == kv { i += 1; }
366 self.val_pos = i;
367 }
368 fn seek_val(&mut self, s: &Self::Storage, val: &V) {
369 if !self.key_valid(s) { return; }
370 let key = s.0[self.key_pos].0.0.clone();
371 self.val_pos = gallop(&s.0, self.val_pos, |u| (&u.0.0, &u.0.1) < (&key, val));
372 }
373 fn rewind_keys(&mut self, _s: &Self::Storage) { self.key_pos = 0; self.val_pos = 0; }
374 fn rewind_vals(&mut self, _s: &Self::Storage) { self.val_pos = self.key_pos; }
375 }
376
377 impl<K, V, T, R> Navigable for VecChunk<K, V, T, R>
378 where K: Ord+Clone+'static, V: Ord+Clone+'static, T: Lattice+Timestamp, R: Ord+Semigroup+'static {
379 type Cursor = VecChunkCursor<K, V, T, R>;
380
381 fn cursor(&self) -> Self::Cursor {
382 VecChunkCursor { key_pos: 0, val_pos: 0, phantom: PhantomData }
383 }
384 }
385
386 impl<K, V, T, R> crate::trace::chunk::NavigableChunk for VecChunk<K, V, T, R>
387 where K: Ord+Clone+'static, V: Ord+Clone+'static, T: Lattice+Timestamp, R: Ord+Semigroup+'static {
388 fn bounds(&self) -> ((&K, &V, &T), (&K, &V, &T)) {
389 let s = &self.0[..];
390 let (f, l) = (&s[0], &s[s.len() - 1]);
391 ((&f.0.0, &f.0.1, &f.1), (&l.0.0, &l.0.1, &l.1))
392 }
393 }
394}
395
396#[cfg(test)]
397mod test {
398 use timely::progress::Antichain;
399 use std::collections::VecDeque;
400 use super::{Chunk, VecChunk};
401 use crate::trace::chunk::merge_chains;
402 use crate::trace::Navigable;
403 use std::rc::Rc;
404
405 fn chunk(updates: Vec<((u64, u64), u64, i64)>) -> VecChunk<u64, u64, u64, i64> {
406 VecChunk(Rc::new(updates))
407 }
408
409 fn flat<I: IntoIterator<Item = VecChunk<u64, u64, u64, i64>>>(chunks: I) -> Vec<((u64, u64), u64, i64)> {
411 chunks.into_iter().flat_map(|c| (*c.0).clone()).collect()
412 }
413
414 #[test]
417 fn extract_partitions_and_grades() {
418 use super::TARGET;
419 use crate::trace::chunk::{is_graded, settle_all};
420
421 let n = 4 * TARGET as u64;
424 let mut input: VecDeque<_> = (0..n).map(|i| chunk(vec![((i, 0), i % 2, 1)])).collect();
425 let frontier = Antichain::from_elem(1u64);
426 let mut residual = Antichain::new();
427 let (mut keep, mut ship) = (VecDeque::new(), VecDeque::new());
428 while !input.is_empty() {
430 VecChunk::extract(&mut input, frontier.borrow(), &mut residual, &mut keep, &mut ship);
431 }
432 let (keep, ship) = (settle_all(keep), settle_all(ship));
433
434 assert_eq!(residual, Antichain::from_elem(1u64));
436 assert!(is_graded(&keep), "ungraded keep: {:?}", keep.iter().map(Chunk::len).collect::<Vec<_>>());
438 assert!(is_graded(&ship), "ungraded ship: {:?}", ship.iter().map(Chunk::len).collect::<Vec<_>>());
439 assert_eq!(keep.iter().map(Chunk::len).sum::<usize>(), n as usize / 2);
441 assert_eq!(ship.iter().map(Chunk::len).sum::<usize>(), n as usize / 2);
442 }
443
444 #[test]
447 fn advance_emits_complete_groups_eagerly() {
448
449 let frontier = Antichain::from_elem(5u64);
450 let c0 = chunk(vec![((0, 0), 0, 1), ((0, 0), 1, 1), ((1, 0), 0, 1)]);
452 let mut input: VecDeque<_> = VecDeque::from([c0]);
453 let mut out = VecDeque::new();
454 VecChunk::advance(&mut input, frontier.borrow(), false, &mut out);
455
456 assert_eq!(input.len(), 1);
458 assert_eq!(Chunk::len(&input[0]), 1);
459 assert_eq!(flat(out), vec![((0, 0), 5, 2)]);
461 }
462
463 #[test]
467 fn advance_resumable_matches_oneshot() {
468
469 let frontier = Antichain::from_elem(3u64);
470 let input = || vec![
472 chunk(vec![((0, 0), 0, 1), ((0, 0), 1, 1), ((1, 0), 0, 1)]),
473 chunk(vec![((1, 0), 5, 1), ((1, 1), 0, 1), ((2, 0), 0, 1)]),
474 chunk(vec![((2, 0), 2, 1), ((2, 0), 9, 1)]),
475 ];
476
477 let oneshot = {
478 let mut q: VecDeque<_> = input().into();
479 let mut out = VecDeque::new();
480 VecChunk::advance(&mut q, frontier.borrow(), false, &mut out);
481 VecChunk::advance(&mut q, frontier.borrow(), true, &mut out);
482 flat(out)
483 };
484 let incremental = {
485 let mut q = VecDeque::new();
486 let mut out = VecDeque::new();
487 for c in input() { q.push_back(c); VecChunk::advance(&mut q, frontier.borrow(), false, &mut out); }
488 VecChunk::advance(&mut q, frontier.borrow(), true, &mut out);
489 flat(out)
490 };
491 assert_eq!(oneshot, incremental);
492 for u in &oneshot { assert!(u.1 >= 3); }
494 }
495
496 #[test]
500 fn advance_single_key_spanning_pushes() {
501
502 let frontier = Antichain::from_elem(100u64);
503 let n = 50u64;
504 let make = || (0..n).map(|t| chunk(vec![((7u64, 0u64), t, 1i64)])).collect::<Vec<_>>();
505
506 let mut q = VecDeque::new();
507 let mut out = VecDeque::new();
508 for c in make() { q.push_back(c); VecChunk::advance(&mut q, frontier.borrow(), false, &mut out); }
509 VecChunk::advance(&mut q, frontier.borrow(), true, &mut out);
510 assert_eq!(flat(out), vec![((7u64, 0u64), 100u64, n as i64)]);
512 }
513
514 #[test]
515 fn merge_chains_consolidates() {
516 let a = chunk(vec![((0, 0), 0, 1), ((1, 0), 0, 1)]);
517 let b = chunk(vec![((0, 0), 0, 1), ((2, 0), 0, 1)]);
518 let mut out = VecDeque::new();
519 merge_chains(vec![a], vec![b], &mut out);
520 assert_eq!(flat(out), vec![((0, 0), 0, 2), ((1, 0), 0, 1), ((2, 0), 0, 1)]);
521 }
522
523 #[test]
527 fn merge_emits_graded_chunks() {
528 use super::TARGET;
529 use crate::trace::chunk::{is_graded, merge_chains, settle_all};
530
531 let n = 4 * TARGET as u64;
533 let evens = chunk((0..n).step_by(2).map(|k| ((k, 0), 0, 1)).collect());
534 let odds = chunk((0..n).step_by(2).map(|k| ((k + 1, 0), 0, 1)).collect());
535
536 let mut out = VecDeque::new();
537 merge_chains(vec![evens], vec![odds], &mut out);
538 let chunks = settle_all(out);
539
540 assert!(is_graded(&chunks), "merge output not graded: {:?}",
541 chunks.iter().map(Chunk::len).collect::<Vec<_>>());
542 let want: Vec<_> = (0..n).map(|k| ((k, 0u64), 0u64, 1i64)).collect();
544 assert_eq!(flat(chunks), want);
545 }
546
547 #[test]
554 fn merge_matches_reference() {
555 use crate::trace::chunk::merge_chains;
556 use crate::consolidation::consolidate_updates;
557
558 let mut seed = 0x2545F4914F6CDD1Du64;
560 let mut rng = move || { seed ^= seed << 13; seed ^= seed >> 7; seed ^= seed << 17; seed };
561
562 fn gen(rng: &mut impl FnMut() -> u64, n: usize) -> Vec<((u64, u64), u64, i64)> {
565 let mut v: Vec<((u64, u64), u64, i64)> = (0..n).map(|_| {
566 let k = rng() % 20; let val = rng() % 3; let t = rng() % 8;
567 let d = if rng() % 4 == 0 { -1 } else { 1 };
568 ((k, val), t, d)
569 }).collect();
570 consolidate_updates(&mut v);
571 v
572 }
573 fn chain(updates: &[((u64, u64), u64, i64)], sz: usize) -> Vec<VecChunk<u64, u64, u64, i64>> {
576 updates.chunks(sz).map(|c| VecChunk(Rc::new(c.to_vec()))).collect()
577 }
578
579 for _ in 0..300 {
580 let n1 = (rng() as usize % 60) + 1;
581 let u1 = gen(&mut rng, n1);
582 let n2 = (rng() as usize % 60) + 1;
583 let u2 = gen(&mut rng, n2);
584 if u1.is_empty() || u2.is_empty() { continue; }
585 let sz = (rng() as usize % 5) + 1; let mut out = VecDeque::new();
588 merge_chains(chain(&u1, sz), chain(&u2, sz), &mut out);
589 let merged = flat(out);
590
591 let mut reference: Vec<_> = u1.iter().chain(u2.iter()).cloned().collect();
592 consolidate_updates(&mut reference);
593
594 assert_eq!(merged, reference, "chunk size {sz}\n u1={u1:?}\n u2={u2:?}");
595 }
596 }
597
598 #[test]
603 fn batch_merger_resumable_matches_reference() {
604 use crate::trace::implementations::spine_fueled::Merger;
605 use crate::trace::chunk::{ChunkBatch, ChunkBatchMerger, is_graded};
606 use crate::trace::cursor::Cursor;
607 use crate::consolidation::consolidate_updates;
608
609 let mut seed = 0x9E3779B97F4A7C15u64;
610 let mut rng = move || { seed ^= seed << 13; seed ^= seed >> 7; seed ^= seed << 17; seed };
611
612 fn gen(rng: &mut impl FnMut() -> u64) -> Vec<((u64, u64), u64, i64)> {
615 let n = rng() as usize % 40 + 1;
616 let mut v: Vec<((u64, u64), u64, i64)> = (0..n).map(|_| {
617 let k = rng() % 10; let val = rng() % 3; let t = rng() % 6;
618 let d = if rng() % 4 == 0 { -1 } else { 1 };
619 ((k, val), t, d)
620 }).collect();
621 consolidate_updates(&mut v);
622 v
623 }
624 fn batch(updates: &[((u64, u64), u64, i64)], sz: usize) -> ChunkBatch<VecChunk<u64, u64, u64, i64>> {
626 let chunks: Vec<_> = updates.chunks(sz).map(|c| VecChunk(Rc::new(c.to_vec()))).collect();
627 ChunkBatch::new(chunks)
628 }
629 fn read(b: &ChunkBatch<VecChunk<u64, u64, u64, i64>>) -> Vec<((u64, u64), u64, i64)> {
631 let mut out = Vec::new();
632 let mut c = b.cursor();
633 while c.key_valid(b) {
634 let k = *c.key(b);
635 while c.val_valid(b) {
636 let v = *c.val(b);
637 c.map_times(b, |t, d| out.push(((k, v), *t, *d)));
638 c.step_val(b);
639 }
640 c.step_key(b);
641 }
642 consolidate_updates(&mut out);
643 out
644 }
645
646 for _ in 0..200 {
647 let u1 = gen(&mut rng);
648 let u2 = gen(&mut rng);
649 if u1.is_empty() || u2.is_empty() { continue; }
650 let sz = (rng() as usize % 4) + 1;
651 let f = rng() % 6;
652 let (s1, s2) = (batch(&u1, sz), batch(&u2, sz));
653 let frontier = Antichain::from_elem(f);
654
655 let mut merger = ChunkBatchMerger::new(&s1, &s2, frontier.borrow());
656 loop {
657 let mut fuel = 1isize; merger.work(&s1, &s2, &mut fuel);
659 if fuel > 0 { break; }
660 }
661 let result = merger.done();
662
663 let chunks: &[VecChunk<u64, u64, u64, i64>] = result.as_ref().map_or(&[], |b| &b.chunks[..]);
665 assert!(is_graded(chunks), "ungraded result: {:?}",
666 chunks.iter().map(Chunk::len).collect::<Vec<_>>());
667 let got = result.as_ref().map_or_else(Vec::new, read);
669 let mut want: Vec<_> = u1.iter().chain(u2.iter()).cloned().collect();
670 for u in want.iter_mut() { u.1 = u.1.max(f); }
671 consolidate_updates(&mut want);
672 assert_eq!(result.is_none(), want.is_empty(), "absence must track emptiness\n u1={u1:?}\n u2={u2:?}\n f={f}");
674 assert_eq!(got, want, "fuel-driven merge mismatch\n u1={u1:?}\n u2={u2:?}\n f={f}");
675 }
676 }
677
678 #[test]
682 fn settle_maximal_packing() {
683 use super::TARGET;
684 use crate::trace::chunk::is_graded;
685
686 let t = TARGET;
689 let sizes = [t / 3, t / 3, t / 3, t, t / 2, t / 2, t, 1, t - 1];
690 let total: usize = sizes.iter().sum();
691 let mut key = 0u64;
692 let mut input = VecDeque::new();
693 let mut output = VecDeque::new();
694 for &s in &sizes {
695 let updates: Vec<_> = (0..s).map(|_| { let k = key; key += 1; ((k, 0u64), 0u64, 1i64) }).collect();
696 input.push_back(chunk(updates));
697 VecChunk::settle(&mut input, false, &mut output);
698 }
699 VecChunk::settle(&mut input, true, &mut output);
700 let chunks: Vec<_> = output.into();
701
702 assert!(is_graded(&chunks), "not graded: {:?}",
703 chunks.iter().map(Chunk::len).collect::<Vec<_>>());
704 let got: Vec<_> = chunks.into_iter().flat_map(|c| (*c.0).clone()).collect();
706 assert_eq!(got.len(), total);
707 assert!(got.windows(2).all(|w| w[0].0.0 < w[1].0.0));
708 }
709
710 #[test]
714 fn cursor_handles_straddle() {
715 use crate::trace::cursor::Cursor;
716 use crate::trace::chunk::ChunkBatch;
717
718 let chunks = vec![
719 chunk(vec![((0, 0), 0, 1), ((1, 0), 0, 1), ((1, 1), 0, 1)]),
720 chunk(vec![((1, 1), 1, 1), ((1, 2), 0, 1)]),
721 chunk(vec![((2, 0), 0, 1)]),
722 ];
723 let batch = ChunkBatch::new(chunks);
724
725 let mut cursor = batch.cursor();
726 let got = cursor.to_vec(&batch, |k| *k, |v| *v);
727 let want = vec![
728 ((0u64, 0u64), vec![(0u64, 1i64)]),
729 ((1, 0), vec![(0, 1)]),
730 ((1, 1), vec![(0, 1), (1, 1)]),
731 ((1, 2), vec![(0, 1)]),
732 ((2, 0), vec![(0, 1)]),
733 ];
734 assert_eq!(got, want);
735 }
736
737 #[test]
740 #[ignore]
741 fn seek_microbench() {
742 use std::time::Instant;
743 use std::hint::black_box;
744 use super::gallop;
745 let n = 1_000_000u64;
746 let data: Vec<((u64, ()), u64, isize)> = (0..n).map(|k| ((3 * k, ()), 0u64, 1isize)).collect();
747 for probes in [100u64, 10_000, 1_000_000] {
748 let targets: Vec<u64> = (0..probes).map(|i| 3 * (i * n / probes)).collect();
749 let best = |f: &dyn Fn() -> u64| {
750 let mut b = std::time::Duration::MAX;
751 for _ in 0..5 { let t = Instant::now(); black_box(f()); b = b.min(t.elapsed()); }
752 b
753 };
754 let data = black_box(&data[..]);
755 let g = best(&|| {
756 let (mut pos, mut acc) = (0usize, 0u64);
757 for &tgt in &targets { pos = gallop(data, pos, |u| u.0.0 < tgt); acc += pos as u64; }
758 acc
759 });
760 let l = best(&|| {
761 let (mut pos, mut acc) = (0usize, 0u64);
762 for &tgt in &targets { while pos < data.len() && data[pos].0.0 < tgt { pos += 1; } acc += pos as u64; }
763 acc
764 });
765 eprintln!("probes={probes:>7}: gallop={g:>12?} linear={l:>12?}");
766 }
767 }
768
769 #[test]
774 fn seek_key_hint_is_direction_independent() {
775 use crate::trace::cursor::Cursor;
776 use crate::trace::chunk::ChunkBatch;
777
778 let chunks: Vec<_> = (0..20u64).map(|k| chunk(vec![((2 * k, 0u64), 0u64, 1i64)])).collect();
780 let batch = ChunkBatch::new(chunks);
781
782 let oracle = |tgt: u64| { let e = tgt + (tgt & 1); (e <= 38).then_some(e) };
784 for start in 0..=40u64 {
785 for tgt in 0..=40u64 {
786 let mut c = batch.cursor();
787 c.seek_key(&batch, &start);
788 c.seek_key(&batch, &tgt);
789 assert_eq!(c.get_key(&batch).copied(), oracle(tgt), "start={start} tgt={tgt}");
790 }
791 }
792 }
793
794}