1pub mod chunker;
15
16use timely::container::{ContainerBuilder, PushInto};
17use timely::progress::frontier::AntichainRef;
18use timely::progress::{frontier::Antichain, Timestamp};
19
20use crate::logging::{BatcherEvent, Logger};
21use crate::batcher::{Batcher, Extract};
22
23pub struct MergeBatcher<Chu, M: Extractor, S> {
29 chunker: Chu,
31 ladder: MergeLadder<M>,
33 frontier: Antichain<M::Time>,
35 sealer: std::marker::PhantomData<S>,
37}
38
39impl<C, Chu, M, S> Batcher<C> for MergeBatcher<Chu, M, S>
40where
41 M: Extractor<Time: Timestamp>,
42 Chu: ContainerBuilder<Container = M::Chunk> + for<'a> PushInto<&'a mut C>,
43 S: Sealer<M::Chunk>,
44{
45 fn insert(&mut self, container: &mut C) {
46 self.chunker.push_into(container);
47 while let Some(chunk) = self.chunker.extract().map(std::mem::take) {
48 self.ladder.insert_chain(vec![chunk]);
49 }
50 }
51}
52
53impl<Chu, M, S> Extract for MergeBatcher<Chu, M, S>
54where
55 M: Extractor<Time: Timestamp>,
56 Chu: ContainerBuilder<Container = M::Chunk>,
57 S: Sealer<M::Chunk>,
58{
59 type Time = M::Time;
60 type Output = S::Output;
61
62 fn extract<'a>(&'a mut self, upper: AntichainRef<'_, M::Time>) -> (Option<S::Output>, AntichainRef<'a, M::Time>) {
67 while let Some(chunk) = self.chunker.finish().map(std::mem::take) {
70 self.ladder.insert_chain(vec![chunk]);
71 }
72
73 let merged = self.ladder.merge_all();
75
76 let mut kept = Vec::new();
78 let mut readied = Vec::new();
79 self.frontier.clear();
80
81 let ladder = &mut self.ladder;
82 ladder.merger.extract(merged, upper, &mut self.frontier, &mut readied, &mut kept, &mut ladder.stash);
83
84 if !kept.is_empty() {
85 ladder.chain_push(kept);
86 }
87
88 ladder.stash.clear();
89
90 (S::seal(&mut readied), self.frontier.borrow())
91 }
92}
93
94impl<Chu: Default, M: Extractor, S> MergeBatcher<Chu, M, S> {
95 pub fn new(logger: Option<Logger>, operator_id: usize) -> Self {
100 Self {
101 chunker: Chu::default(),
102 ladder: MergeLadder::new(logger, operator_id),
103 frontier: Antichain::new(),
104 sealer: std::marker::PhantomData,
105 }
106 }
107}
108
109pub struct MergeLadder<M: Merger> {
114 chains: Vec<(usize, Vec<M::Chunk>)>,
122 stash: Vec<M::Chunk>,
124 merger: M,
126 logger: Option<Logger>,
128 operator_id: usize,
130}
131
132impl<M: Merger> MergeLadder<M> {
133 pub fn new(logger: Option<Logger>, operator_id: usize) -> Self {
138 Self {
139 chains: Vec::new(),
140 stash: Vec::new(),
141 merger: M::default(),
142 logger,
143 operator_id,
144 }
145 }
146
147 pub fn insert_chain(&mut self, chain: Vec<M::Chunk>) {
150 if !chain.is_empty() {
151 self.chain_push(chain);
152 while self.chains.len() > 1 && (self.chains[self.chains.len() - 1].0 >= self.chains[self.chains.len() - 2].0 / 2) {
153 let list1 = self.chain_pop().unwrap();
154 let list2 = self.chain_pop().unwrap();
155 let merged = self.merge_by(list1, list2);
156 self.chain_push(merged);
157 }
158 }
159 }
160
161 pub fn merge_all(&mut self) -> Vec<M::Chunk> {
163 while self.chains.len() > 1 {
164 let list1 = self.chain_pop().unwrap();
165 let list2 = self.chain_pop().unwrap();
166 let merged = self.merge_by(list1, list2);
167 self.chain_push(merged);
168 }
169 self.chain_pop().unwrap_or_default()
170 }
171
172 pub fn chains(&self) -> impl Iterator<Item = &[M::Chunk]> {
174 self.chains.iter().map(|(_, chain)| &chain[..])
175 }
176
177 pub fn retain_chains(&mut self, mut logic: impl FnMut(&mut Vec<M::Chunk>)) {
183 let mut chains = std::mem::take(&mut self.chains);
184 for (_weight, chain) in chains.iter_mut() {
185 self.account(chain.iter().map(Self::record), -1);
186 logic(chain);
187 self.account(chain.iter().map(Self::record), 1);
188 }
189 chains.retain(|(_weight, chain)| !chain.is_empty());
190 self.chains = chains;
191 }
192
193 fn merge_by(&mut self, list1: Vec<M::Chunk>, list2: Vec<M::Chunk>) -> Vec<M::Chunk> {
195 let mut output = Vec::with_capacity(list1.len() + list2.len());
197 self.merger.merge(list1, list2, &mut output, &mut self.stash);
198
199 output
200 }
201
202 #[inline]
204 fn chain_pop(&mut self) -> Option<Vec<M::Chunk>> {
205 let (_weight, chain) = self.chains.pop()?;
206 self.account(chain.iter().map(Self::record), -1);
207 Some(chain)
208 }
209
210 #[inline]
214 fn chain_push(&mut self, chain: Vec<M::Chunk>) {
215 let weight = chain.iter().map(M::len).sum();
216 self.account(chain.iter().map(Self::record), 1);
217 self.chains.push((weight, chain));
218 }
219
220 #[inline]
223 fn record(chunk: &M::Chunk) -> (usize, usize, usize, usize) {
224 let (size, capacity, allocations) = M::allocation(chunk);
225 (M::len(chunk), size, capacity, allocations)
226 }
227
228 #[inline]
233 fn account<I: IntoIterator<Item = (usize, usize, usize, usize)>>(&self, items: I, diff: isize) {
234 if let Some(logger) = &self.logger {
235 let (mut records, mut size, mut capacity, mut allocations) = (0isize, 0isize, 0isize, 0isize);
236 for (records_, size_, capacity_, allocations_) in items {
237 records = records.saturating_add_unsigned(records_);
238 size = size.saturating_add_unsigned(size_);
239 capacity = capacity.saturating_add_unsigned(capacity_);
240 allocations = allocations.saturating_add_unsigned(allocations_);
241 }
242 logger.log(BatcherEvent {
243 operator: self.operator_id,
244 records_diff: records * diff,
245 size_diff: size * diff,
246 capacity_diff: capacity * diff,
247 allocations_diff: allocations * diff,
248 })
249 }
250 }
251}
252
253impl<M: Merger> Drop for MergeLadder<M> {
254 fn drop(&mut self) {
255 while self.chain_pop().is_some() {}
257 }
258}
259
260pub trait Merger: Default {
262 type Chunk: Default;
264 fn merge(&mut self, list1: Vec<Self::Chunk>, list2: Vec<Self::Chunk>, output: &mut Vec<Self::Chunk>, stash: &mut Vec<Self::Chunk>);
266
267 fn len(chunk: &Self::Chunk) -> usize;
273
274 fn allocation(_chunk: &Self::Chunk) -> (usize, usize, usize) { (0, 0, 0) }
280}
281
282pub trait Extractor: Merger {
284 type Time;
286 fn extract(
288 &mut self,
289 merged: Vec<Self::Chunk>,
290 upper: AntichainRef<Self::Time>,
291 frontier: &mut Antichain<Self::Time>,
292 readied: &mut Vec<Self::Chunk>,
293 kept: &mut Vec<Self::Chunk>,
294 stash: &mut Vec<Self::Chunk>,
295 );
296}
297
298pub trait Sealer<C> {
304 type Output;
306
307 fn seal(chain: &mut Vec<C>) -> Option<Self::Output>;
315}
316
317pub mod vec {
319
320 use std::marker::PhantomData;
321 use timely::container::SizableContainer;
322 use timely::progress::frontier::{Antichain, AntichainRef};
323 use timely::PartialOrder;
324 use crate::batcher::merge::{Extractor, Merger};
325
326 pub struct VecMerger<D, T, R> {
328 _marker: PhantomData<(D, T, R)>,
329 }
330
331 impl<D, T, R> Default for VecMerger<D, T, R> {
332 fn default() -> Self { Self { _marker: PhantomData } }
333 }
334
335 impl<D, T, R> VecMerger<D, T, R> {
336 fn target_capacity() -> usize {
341 timely::container::buffer::default_capacity::<(D, T, R)>().next_power_of_two()
342 }
343 fn empty(&self, stash: &mut Vec<Vec<(D, T, R)>>) -> Vec<(D, T, R)> {
345 let target = Self::target_capacity();
346 let mut container = stash.pop().unwrap_or_default();
347 container.clear();
348 if container.capacity() != target {
350 container = Vec::with_capacity(target);
351 }
352 container
353 }
354 fn refill(queue: &mut std::collections::VecDeque<(D, T, R)>, iter: &mut impl Iterator<Item = Vec<(D, T, R)>>, stash: &mut Vec<Vec<(D, T, R)>>) {
356 if queue.is_empty() {
357 let target = Self::target_capacity();
358 if stash.len() < 2 {
359 let mut recycled = Vec::from(std::mem::take(queue));
360 recycled.clear();
361 if recycled.capacity() == target {
362 stash.push(recycled);
363 }
364 }
365 if let Some(chunk) = iter.next() {
366 *queue = std::collections::VecDeque::from(chunk);
367 }
368 }
369 }
370 }
371
372 impl<D, T, R> Merger for VecMerger<D, T, R>
373 where
374 D: Ord + Clone + 'static,
375 T: Ord + Clone + PartialOrder + 'static,
376 R: crate::difference::Semigroup + 'static,
377 {
378 type Chunk = Vec<(D, T, R)>;
379
380 fn merge(
381 &mut self,
382 list1: Vec<Vec<(D, T, R)>>,
383 list2: Vec<Vec<(D, T, R)>>,
384 output: &mut Vec<Vec<(D, T, R)>>,
385 stash: &mut Vec<Vec<(D, T, R)>>,
386 ) {
387 use std::cmp::Ordering;
388 use std::collections::VecDeque;
389
390 let mut iter1 = list1.into_iter();
391 let mut iter2 = list2.into_iter();
392 let mut q1 = VecDeque::<(D,T,R)>::from(iter1.next().unwrap_or_default());
393 let mut q2 = VecDeque::<(D,T,R)>::from(iter2.next().unwrap_or_default());
394
395 let mut result = self.empty(stash);
396
397 while let (Some((d1, t1, _)), Some((d2, t2, _))) = (q1.front(), q2.front()) {
399 match (d1, t1).cmp(&(d2, t2)) {
400 Ordering::Less => {
401 result.push(q1.pop_front().unwrap());
402 }
403 Ordering::Greater => {
404 result.push(q2.pop_front().unwrap());
405 }
406 Ordering::Equal => {
407 let (d, t, mut r1) = q1.pop_front().unwrap();
408 let (_, _, r2) = q2.pop_front().unwrap();
409 r1.plus_equals(&r2);
410 if !r1.is_zero() {
411 result.push((d, t, r1));
412 }
413 }
414 }
415
416 if result.at_capacity() {
417 output.push(std::mem::take(&mut result));
418 result = self.empty(stash);
419 }
420
421 if q1.is_empty() { Self::refill(&mut q1, &mut iter1, stash); }
423 if q2.is_empty() { Self::refill(&mut q2, &mut iter2, stash); }
424 }
425
426 if !result.is_empty() { output.push(result); }
428 for q in [q1, q2] {
429 if !q.is_empty() { output.push(Vec::from(q)); }
430 }
431 output.extend(iter1);
432 output.extend(iter2);
433 }
434
435 fn len(chunk: &Vec<(D, T, R)>) -> usize { chunk.len() }
436 }
437
438 impl<D, T, R> Extractor for VecMerger<D, T, R>
439 where
440 D: Ord + Clone + 'static,
441 T: Ord + Clone + PartialOrder + 'static,
442 R: crate::difference::Semigroup + 'static,
443 {
444 type Time = T;
445
446 fn extract(
447 &mut self,
448 merged: Vec<Vec<(D, T, R)>>,
449 upper: AntichainRef<T>,
450 frontier: &mut Antichain<T>,
451 ship: &mut Vec<Vec<(D, T, R)>>,
452 kept: &mut Vec<Vec<(D, T, R)>>,
453 stash: &mut Vec<Vec<(D, T, R)>>,
454 ) {
455 let mut keep = self.empty(stash);
456 let mut ready = self.empty(stash);
457
458 for mut chunk in merged {
459 for (data, time, diff) in chunk.drain(..) {
461 if upper.less_equal(&time) {
462 frontier.insert_with(&time, |time| time.clone());
463 keep.push((data, time, diff));
464 } else {
465 ready.push((data, time, diff));
466 }
467 if keep.at_capacity() {
468 kept.push(std::mem::take(&mut keep));
469 keep = self.empty(stash);
470 }
471 if ready.at_capacity() {
472 ship.push(std::mem::take(&mut ready));
473 ready = self.empty(stash);
474 }
475 }
476 if chunk.capacity() == Self::target_capacity() {
478 stash.push(chunk);
479 }
480 }
481 if !keep.is_empty() { kept.push(keep); }
482 if !ready.is_empty() { ship.push(ready); }
483 }
484 }
485}
486
487#[cfg(test)]
488mod test {
489 use timely::progress::frontier::Antichain;
490 use crate::batcher::Extract;
491 use super::{MergeBatcher, Sealer};
492 use super::vec::VecMerger;
493 use crate::batcher::merge::chunker::ContainerChunker;
494
495 type In = Vec<((u64, ()), u64, i64)>;
496 type Bt = MergeBatcher<ContainerChunker<In>, VecMerger<(u64, ()), u64, i64>, TestSealer>;
497
498 struct TestSealer;
499
500 impl Sealer<In> for TestSealer {
501 type Output = Vec<In>;
502
503 fn seal(chain: &mut Vec<In>) -> Option<Self::Output> {
504 (!chain.is_empty()).then(|| std::mem::take(chain))
505 }
506 }
507
508 #[test]
514 fn frontier_is_post_consolidation() {
515 let mut b = Bt::new(None, 0);
516 b.ladder.chain_push(vec![vec![((100u64, ()), 5u64, 1i64), ((200u64, ()), 7u64, 1i64)]]);
517 b.ladder.chain_push(vec![vec![((100u64, ()), 5u64, -1i64)]]);
518 let (_, retained) = Extract::extract(&mut b, Antichain::from_elem(3).borrow());
519 let got: Vec<u64> = retained.iter().cloned().collect();
520 assert_eq!(got, vec![7u64],
521 "frontier held a capability at t=5, which consolidates to zero (got {got:?})");
522 }
523
524 #[test]
526 fn frontier_survivor_minimum() {
527 let mut b = Bt::new(None, 0);
528 b.ladder.chain_push(vec![vec![((100u64, ()), 5u64, 1i64)]]);
529 b.ladder.chain_push(vec![vec![((200u64, ()), 7u64, 1i64)]]);
530 let (_, retained) = Extract::extract(&mut b, Antichain::from_elem(3).borrow());
531 let got: Vec<u64> = retained.iter().cloned().collect();
532 assert_eq!(got, vec![5u64]);
533 }
534}