1use timely::progress::frontier::AntichainRef;
12use timely::progress::{frontier::Antichain, Timestamp};
13use timely::container::PushInto;
14
15use crate::logging::{BatcherEvent, Logger};
16use crate::trace::{Batcher, Description};
17
18pub struct MergeBatcher<M: Merger> {
20 chains: Vec<(usize, Vec<M::Chunk>)>,
28 stash: Vec<M::Chunk>,
30 merger: M,
32 lower: Antichain<M::Time>,
34 frontier: Antichain<M::Time>,
36 logger: Option<Logger>,
38 operator_id: usize,
40}
41
42impl<M> Batcher for MergeBatcher<M>
43where
44 M: Merger<Time: Timestamp>,
45{
46 type Time = M::Time;
47 type Output = M::Chunk;
48
49 fn new(logger: Option<Logger>, operator_id: usize) -> Self {
50 Self {
51 logger,
52 operator_id,
53 merger: M::default(),
54 chains: Vec::new(),
55 stash: Vec::new(),
56 frontier: Antichain::new(),
57 lower: Antichain::from_elem(M::Time::minimum()),
58 }
59 }
60
61 fn seal(&mut self, upper: Antichain<M::Time>) -> (Vec<Self::Output>, Description<M::Time>) {
66 while self.chains.len() > 1 {
68 let list1 = self.chain_pop().unwrap();
69 let list2 = self.chain_pop().unwrap();
70 let merged = self.merge_by(list1, list2);
71 self.chain_push(merged);
72 }
73 let merged = self.chain_pop().unwrap_or_default();
74
75 let mut kept = Vec::new();
77 let mut readied = Vec::new();
78 self.frontier.clear();
79
80 self.merger.extract(merged, upper.borrow(), &mut self.frontier, &mut readied, &mut kept, &mut self.stash);
81
82 if !kept.is_empty() {
83 self.chain_push(kept);
84 }
85
86 self.stash.clear();
87
88 let description = Description::new(self.lower.clone(), upper.clone(), Antichain::from_elem(M::Time::minimum()));
89 self.lower = upper;
90 (readied, description)
91 }
92
93 #[inline]
95 fn frontier(&mut self) -> AntichainRef<'_, M::Time> {
96 self.frontier.borrow()
97 }
98}
99
100impl<M: Merger> PushInto<M::Chunk> for MergeBatcher<M> {
101 fn push_into(&mut self, chunk: M::Chunk) {
102 self.insert_chain(vec![chunk]);
103 }
104}
105
106impl<M: Merger> MergeBatcher<M> {
107 fn insert_chain(&mut self, chain: Vec<M::Chunk>) {
110 if !chain.is_empty() {
111 self.chain_push(chain);
112 while self.chains.len() > 1 && (self.chains[self.chains.len() - 1].0 >= self.chains[self.chains.len() - 2].0 / 2) {
113 let list1 = self.chain_pop().unwrap();
114 let list2 = self.chain_pop().unwrap();
115 let merged = self.merge_by(list1, list2);
116 self.chain_push(merged);
117 }
118 }
119 }
120
121 fn merge_by(&mut self, list1: Vec<M::Chunk>, list2: Vec<M::Chunk>) -> Vec<M::Chunk> {
123 let mut output = Vec::with_capacity(list1.len() + list2.len());
125 self.merger.merge(list1, list2, &mut output, &mut self.stash);
126
127 output
128 }
129
130 #[inline]
132 fn chain_pop(&mut self) -> Option<Vec<M::Chunk>> {
133 let (_weight, chain) = self.chains.pop()?;
134 self.account(chain.iter().map(Self::record), -1);
135 Some(chain)
136 }
137
138 #[inline]
142 fn chain_push(&mut self, chain: Vec<M::Chunk>) {
143 let weight = chain.iter().map(M::len).sum();
144 self.account(chain.iter().map(Self::record), 1);
145 self.chains.push((weight, chain));
146 }
147
148 #[inline]
151 fn record(chunk: &M::Chunk) -> (usize, usize, usize, usize) {
152 let (size, capacity, allocations) = M::allocation(chunk);
153 (M::len(chunk), size, capacity, allocations)
154 }
155
156 #[inline]
161 fn account<I: IntoIterator<Item = (usize, usize, usize, usize)>>(&self, items: I, diff: isize) {
162 if let Some(logger) = &self.logger {
163 let (mut records, mut size, mut capacity, mut allocations) = (0isize, 0isize, 0isize, 0isize);
164 for (records_, size_, capacity_, allocations_) in items {
165 records = records.saturating_add_unsigned(records_);
166 size = size.saturating_add_unsigned(size_);
167 capacity = capacity.saturating_add_unsigned(capacity_);
168 allocations = allocations.saturating_add_unsigned(allocations_);
169 }
170 logger.log(BatcherEvent {
171 operator: self.operator_id,
172 records_diff: records * diff,
173 size_diff: size * diff,
174 capacity_diff: capacity * diff,
175 allocations_diff: allocations * diff,
176 })
177 }
178 }
179}
180
181impl<M: Merger> Drop for MergeBatcher<M> {
182 fn drop(&mut self) {
183 while self.chain_pop().is_some() {}
185 }
186}
187
188pub trait Merger: Default {
190 type Chunk: Default;
192 type Time;
194 fn merge(&mut self, list1: Vec<Self::Chunk>, list2: Vec<Self::Chunk>, output: &mut Vec<Self::Chunk>, stash: &mut Vec<Self::Chunk>);
196 fn extract(
198 &mut self,
199 merged: Vec<Self::Chunk>,
200 upper: AntichainRef<Self::Time>,
201 frontier: &mut Antichain<Self::Time>,
202 readied: &mut Vec<Self::Chunk>,
203 kept: &mut Vec<Self::Chunk>,
204 stash: &mut Vec<Self::Chunk>,
205 );
206
207 fn len(chunk: &Self::Chunk) -> usize;
213
214 fn allocation(_chunk: &Self::Chunk) -> (usize, usize, usize) { (0, 0, 0) }
220}
221
222pub mod vec {
224
225 use std::marker::PhantomData;
226 use timely::container::SizableContainer;
227 use timely::progress::frontier::{Antichain, AntichainRef};
228 use timely::PartialOrder;
229 use crate::trace::implementations::merge_batcher::Merger;
230
231 pub struct VecMerger<D, T, R> {
233 _marker: PhantomData<(D, T, R)>,
234 }
235
236 impl<D, T, R> Default for VecMerger<D, T, R> {
237 fn default() -> Self { Self { _marker: PhantomData } }
238 }
239
240 impl<D, T, R> VecMerger<D, T, R> {
241 fn target_capacity() -> usize {
246 timely::container::buffer::default_capacity::<(D, T, R)>().next_power_of_two()
247 }
248 fn empty(&self, stash: &mut Vec<Vec<(D, T, R)>>) -> Vec<(D, T, R)> {
250 let target = Self::target_capacity();
251 let mut container = stash.pop().unwrap_or_default();
252 container.clear();
253 if container.capacity() != target {
255 container = Vec::with_capacity(target);
256 }
257 container
258 }
259 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)>>) {
261 if queue.is_empty() {
262 let target = Self::target_capacity();
263 if stash.len() < 2 {
264 let mut recycled = Vec::from(std::mem::take(queue));
265 recycled.clear();
266 if recycled.capacity() == target {
267 stash.push(recycled);
268 }
269 }
270 if let Some(chunk) = iter.next() {
271 *queue = std::collections::VecDeque::from(chunk);
272 }
273 }
274 }
275 }
276
277 impl<D, T, R> Merger for VecMerger<D, T, R>
278 where
279 D: Ord + Clone + 'static,
280 T: Ord + Clone + PartialOrder + 'static,
281 R: crate::difference::Semigroup + 'static,
282 {
283 type Chunk = Vec<(D, T, R)>;
284 type Time = T;
285
286 fn merge(
287 &mut self,
288 list1: Vec<Vec<(D, T, R)>>,
289 list2: Vec<Vec<(D, T, R)>>,
290 output: &mut Vec<Vec<(D, T, R)>>,
291 stash: &mut Vec<Vec<(D, T, R)>>,
292 ) {
293 use std::cmp::Ordering;
294 use std::collections::VecDeque;
295
296 let mut iter1 = list1.into_iter();
297 let mut iter2 = list2.into_iter();
298 let mut q1 = VecDeque::<(D,T,R)>::from(iter1.next().unwrap_or_default());
299 let mut q2 = VecDeque::<(D,T,R)>::from(iter2.next().unwrap_or_default());
300
301 let mut result = self.empty(stash);
302
303 while let (Some((d1, t1, _)), Some((d2, t2, _))) = (q1.front(), q2.front()) {
305 match (d1, t1).cmp(&(d2, t2)) {
306 Ordering::Less => {
307 result.push(q1.pop_front().unwrap());
308 }
309 Ordering::Greater => {
310 result.push(q2.pop_front().unwrap());
311 }
312 Ordering::Equal => {
313 let (d, t, mut r1) = q1.pop_front().unwrap();
314 let (_, _, r2) = q2.pop_front().unwrap();
315 r1.plus_equals(&r2);
316 if !r1.is_zero() {
317 result.push((d, t, r1));
318 }
319 }
320 }
321
322 if result.at_capacity() {
323 output.push(std::mem::take(&mut result));
324 result = self.empty(stash);
325 }
326
327 if q1.is_empty() { Self::refill(&mut q1, &mut iter1, stash); }
329 if q2.is_empty() { Self::refill(&mut q2, &mut iter2, stash); }
330 }
331
332 if !result.is_empty() { output.push(result); }
334 for q in [q1, q2] {
335 if !q.is_empty() { output.push(Vec::from(q)); }
336 }
337 output.extend(iter1);
338 output.extend(iter2);
339 }
340
341 fn extract(
342 &mut self,
343 merged: Vec<Vec<(D, T, R)>>,
344 upper: AntichainRef<T>,
345 frontier: &mut Antichain<T>,
346 ship: &mut Vec<Vec<(D, T, R)>>,
347 kept: &mut Vec<Vec<(D, T, R)>>,
348 stash: &mut Vec<Vec<(D, T, R)>>,
349 ) {
350 let mut keep = self.empty(stash);
351 let mut ready = self.empty(stash);
352
353 for mut chunk in merged {
354 for (data, time, diff) in chunk.drain(..) {
356 if upper.less_equal(&time) {
357 frontier.insert_with(&time, |time| time.clone());
358 keep.push((data, time, diff));
359 } else {
360 ready.push((data, time, diff));
361 }
362 if keep.at_capacity() {
363 kept.push(std::mem::take(&mut keep));
364 keep = self.empty(stash);
365 }
366 if ready.at_capacity() {
367 ship.push(std::mem::take(&mut ready));
368 ready = self.empty(stash);
369 }
370 }
371 if chunk.capacity() == Self::target_capacity() {
373 stash.push(chunk);
374 }
375 }
376 if !keep.is_empty() { kept.push(keep); }
377 if !ready.is_empty() { ship.push(ready); }
378 }
379
380 fn len(chunk: &Vec<(D, T, R)>) -> usize { chunk.len() }
381 }
382}
383
384#[cfg(test)]
385mod test {
386 use timely::progress::frontier::Antichain;
387 use crate::trace::Batcher;
388 use super::MergeBatcher;
389 use super::vec::VecMerger;
390
391 type Bt = MergeBatcher<VecMerger<u64, u64, i64>>;
392
393 #[test]
399 fn frontier_is_post_consolidation() {
400 let mut b = Bt::new(None, 0);
401 b.chain_push(vec![vec![(100u64, 5u64, 1i64), (200u64, 7u64, 1i64)]]);
402 b.chain_push(vec![vec![(100u64, 5u64, -1i64)]]);
403 let _ = b.seal(Antichain::from_elem(3));
404 let got: Vec<u64> = b.frontier().iter().cloned().collect();
405 assert_eq!(got, vec![7u64],
406 "frontier held a capability at t=5, which consolidates to zero (got {got:?})");
407 }
408
409 #[test]
411 fn frontier_survivor_minimum() {
412 let mut b = Bt::new(None, 0);
413 b.chain_push(vec![vec![(100u64, 5u64, 1i64)]]);
414 b.chain_push(vec![vec![(200u64, 7u64, 1i64)]]);
415 let _ = b.seal(Antichain::from_elem(3));
416 let got: Vec<u64> = b.frontier().iter().cloned().collect();
417 assert_eq!(got, vec![5u64]);
418 }
419}