From 11ce61094b73fd0baee2961ccceaa91df64e50c2 Mon Sep 17 00:00:00 2001 From: Frank McSherry Date: Wed, 29 Jul 2026 19:37:16 -0400 Subject: [PATCH 1/4] int_proxy reduce: split retire into a named phase struct `retire` held fourteen scratch buffers as inline locals and distinguished its two phases by comment alone. Those buffers and the times a retire reasons against now live on a `Retire` struct, whose `run` walks the windows and whose `determine` / `apply` / `flush` are the phases the comments named. Pure motion; the only behavioral difference is that `tile_deltas` is cleared at the start of `apply` rather than the start of the window, which nothing between the two observes. Also drops a vestigial `'static` on `ProxyReduceBackend::ROut` and corrects a comment that credited `drain` for what `append` does. Co-Authored-By: Claude Opus 5 --- .../src/operators/int_proxy/reduce.rs | 442 +++++++++++------- 1 file changed, 267 insertions(+), 175 deletions(-) diff --git a/differential-dataflow/src/operators/int_proxy/reduce.rs b/differential-dataflow/src/operators/int_proxy/reduce.rs index ee206a904..eec397353 100644 --- a/differential-dataflow/src/operators/int_proxy/reduce.rs +++ b/differential-dataflow/src/operators/int_proxy/reduce.rs @@ -51,7 +51,7 @@ pub trait ProxyReduceBackend> /// Diff type presented for the input. type RIn: Semigroup; /// Diff type of the output. - type ROut: Semigroup + 'static; + type ROut: Semigroup; /// Hash keys and associated times in the instance's novel input batches. /// @@ -152,201 +152,293 @@ where let (tile_descs, tile_held, tile_of) = tile_descriptions(lower, upper, &held_elems); self.backend.begin(&tile_descs); - let mut new_pending: BTreeMap> = BTreeMap::new(); + let mut retire = Retire::new(&mut self.backend, instance, upper, held_elems, tile_of, &self.pending); + retire.run(&changed, &seeds); + let new_pending = retire.new_pending; + self.pending = new_pending; + let produced: Vec<(B1::Time, B2)> = tile_held.into_iter().zip(self.backend.finish()).collect(); + let mut frontier = Antichain::new(); + for times in self.pending.values() { + for t in times { + frontier.insert_ref(t); + } + } + (produced, frontier) + } +} + +/// One retire in progress: the backend session it drives, the times it reasons against, and the +/// scratch its two phases reuse. +/// +/// The retire proceeds window by window (see [`run`](Self::run)), each window determined then +/// applied then flushed. All buffers below are cleared per window, round, or moment rather than +/// reallocated. See the profiling note on [`DiscoverScratch`]: fresh per-key and per-round `Vec`s +/// were the dominant cost. +struct Retire<'a, B1, B2, Bk> +where + B1: BatchReader, + B2: BatchReader