diff --git a/src/vdbe/hash_agg.rs b/src/vdbe/hash_agg.rs index 5508cc4..5a509b1 100644 --- a/src/vdbe/hash_agg.rs +++ b/src/vdbe/hash_agg.rs @@ -119,6 +119,22 @@ pub(crate) struct HashAggState { /// Iteration position within `order`. pos: usize, frozen: bool, + /// Scratch buffer for `HashAggFind`'s per-row key-source values, + /// reused across rows via take/give-back instead of allocating a + /// fresh `Vec` each call. + values_scratch: Vec, + /// Scratch buffer for `group_key_into`'s canonical key bytes, reused + /// the same way. Cloned into `index` only when a row starts a new + /// group. + key_bytes_scratch: Vec, + /// Scratch buffer for `group_key_into`'s decoded key values, reused + /// the same way. Cloned into a new `GroupSlot` only when a row + /// starts a new group. + key_values_scratch: Vec, + /// Scratch buffer for `HashAggStep`'s per-row aggregate arguments, + /// reused across rows via take/give-back. Never cloned: arguments + /// are only read by reference into `aggregate::step`. + args_scratch: Vec, } // Methods rather than free functions so the borrow of `self` elides — @@ -214,17 +230,34 @@ fn exact_integer_real(r: f64) -> Option { } /// This row's group key: the `keys`-named columns of `values`, each -/// with its comparison affinity applied, as both canonical bytes (the -/// hash-map key) and values (kept for `HashAggRewind`'s ordering). -fn group_key(values: &[Value], keys: &[GroupKeyColumn]) -> (Vec, Vec) { - let mut bytes = Vec::with_capacity(keys.len().saturating_mul(9)); - let mut key_values = Vec::with_capacity(keys.len()); +/// with its comparison affinity applied, written into `bytes` (the +/// hash-map key) and `key_values` (kept for `HashAggRewind`'s +/// ordering) — both cleared and reused in place rather than allocated +/// fresh, so a caller can reuse the same pair of buffers across rows. +fn group_key_into( + values: &[Value], + keys: &[GroupKeyColumn], + bytes: &mut Vec, + key_values: &mut Vec, +) { + bytes.clear(); + key_values.clear(); for key in keys { let mut value = values.get(key.index).cloned().unwrap_or(Value::Null); apply_affinity(&mut value, Affinity::from_p4_byte(key.affinity)); - push_key_bytes(&mut bytes, &value, key.collation); + push_key_bytes(bytes, &value, key.collation); key_values.push(value); } +} + +/// Test/non-hot-path convenience wrapper over [`group_key_into`] that +/// allocates fresh buffers — the hot path (`hash_agg_find`) reuses +/// scratch buffers directly instead. +#[cfg(test)] +fn group_key(values: &[Value], keys: &[GroupKeyColumn]) -> (Vec, Vec) { + let mut bytes = Vec::new(); + let mut key_values = Vec::new(); + group_key_into(values, keys, &mut bytes, &mut key_values); (bytes, key_values) } @@ -250,6 +283,10 @@ pub fn hash_agg_open(vm: &mut Vm, instr: &Instruction) -> Result Result Result *pos, None => { let pos = state.groups.len(); state.groups.push(GroupSlot { row: blob, - key_values, + key_values: key_values.clone(), accumulators: Vec::new(), }); - state.index.insert(bytes, pos); + state.index.insert(bytes.clone(), pos); pos } }; state.current = Some(position); + state.values_scratch = values; + state.key_bytes_scratch = bytes; + state.key_values_scratch = key_values; Ok(Step::Next) } @@ -351,7 +397,11 @@ pub fn hash_agg_step(vm: &mut Vm, instr: &Instruction) -> Result Result