compute: pack accumulable reduce accumulators at their natural width - #38716
compute: pack accumulable reduce accumulators at their natural width#38716djahandarie wants to merge 1 commit into
Conversation
The accumulable reduce's input arrangement carries one `Accum` per aggregate in its diff. `Accum` is an enum sized for its widest variant (`Numeric`, a 64 byte `Decimal<27>` plus four counters) and aligned to 16 by the `i128` in its siblings, so every aggregate costs 112 bytes even where `SimpleNumber` needs 24, `Float` 48, or `Bool` 16. A reduce with seven sums over integer and double columns pays about 816 bytes per group where 279 would do, and that arrangement dominates the dataflow's memory. Add `Accums`, a newtype over `Vec<u8>` that stores the same accumulators as a self-delimiting sequence of tagged slots at their natural widths, and let `enable_packed_accumulable_diff` select it as the arrangement diff. `Accum` stays the arithmetic type: every operation on `Accums` decodes a slot, reuses `Accum`'s `Semigroup`, `IsZero`, and `Multiply` impls, and re-encodes, so the accumulation domain (`i128`, `Decimal<27>`) and every output type are unchanged. Rendering is generic over a small `AccumulableDiff` trait implemented for both `Vec<Accum>` and `Accums`; the `Vec<Accum>` path is untouched. The flag defaults off in production and on in CI. Locally, on 100k groups with four integer and three double sums, the input arrangement drops from 835 to 298 bytes per record and the output arrangement is byte-identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Reviewed at 5b11372. Relaying the Buildkite results first, since they are not visible outside the org. Clippy fails on both linux and macOS, and it is the only genuine failure in the change. Both errors sit in the new test file, and neither surfaces under The second one fires because
Everything else passed, including On the change itself. The design reads right for the constraint it works under, because
Net improvement to the codebase. Good to merge once clippy is green. 🤖 Posted by Claude Code |
|
Thank you for the review! It looks like #38718 (using columnar for Accum) is more on strategy, so I'll assume further work will continue there, but if something about that PR doesn't work out I'm happy to pick this one back up. |
Motivation
The accumulable reduce (
sum,count,any,all) keeps oneAccumper aggregate in the diff of its input arrangement.Accumis an enum sized for its widest variant (Numeric: a 64 byteDecimal<27>plus fouri64counters) and aligned to 16 by thei128in its sibling variants, sosize_of::<Accum>() == 112regardless of which variant a slot holds:SimpleNumber(integer sums,count)FloatBool(any/all)NumericA reduce with seven sums over
integeranddouble precisioncolumns pays about 816 bytes per group for the diff alone. This change came out of a production dataflow with exactly that shape over roughly half a billion groups, whose accumulable arrangement holds about 400 GB, most of the dataflow's memory; at the per-record saving measured below, this layout should take well over 200 GB of that back.Description
Adds
Accums, a newtype overVec<u8>that stores the same accumulators as a self-delimiting sequence of slots, each a one-byte tag followed by that variant's fixed-size payload (16/24/48/95 bytes), and lets the newenable_packed_accumulable_diffflag select it as the arrangement diff type.Non-obvious decisions:
Accumis untouched and remains the arithmetic type. Every operation onAccumsdecodes a slot toAccum, uses the existingSemigroup/IsZero/Multiply<Diff>impls, and re-encodes. The accumulation domain (Overflowing<i128>,NumericAgg = Decimal<27>) is stored at full width andfinalize_accumand all output types are unchanged, so this is a storage-layout change only.Numericslots go throughDecimal::to_raw_parts/from_raw_parts, written field by field, so no struct padding enters the encoding.AccumulableDifftrait implemented for bothVec<Accum>andAccums. TheVec<Accum>body was moved into the generic function without behavioral edits. The trait carries a per-planLayout(slot byte offsets forAccums,()forVec<Accum>) computed once next to the zero diff, so per-row slot writes inexplode_onestay O(1).decode_slotbounds its reader to the slot, so a field list that ever disagrees withslot_lenpanics in every build profile instead of reading into the neighbouring slot.mzcomposeVariableSystemParameter, also randomized underCI_SYSTEM_PARAMETERS=random). It is listed inKNOWN_MISSING_FROM_LD, so enabling it in cloud needs a LaunchDarkly flag first.Size note: about half of the insertions are tests (
reduce/tests.rs); the accumulable-reduce test module moved out of line to satisfy themz-computecrate's inline-test-module limit.Verification
New unit tests in
src/compute/src/render/reduce/tests.rs:accums_slot_sizespins the encoded size of every variant.accums_roundtripround-trips every variant through encode/decode at extremes (i128::MIN/MAX, negative counters, large and small numeric exponents).accums_region_roundtripdrivesAccumsRegion, the onlyunsafein the change, through aColumnStack: copy, read back,heap_size,clear, copy again.accums_empty_is_identitychecks the empty value behaves as DD'sVec<R>zero.accums_agree_with_vec_accumapplies seeded randomplus_equals/multiplysequences over every accumulableAggregateFunc(nulls, float and numeric specials, negative diffs, mixed multi-slot layouts) to aVec<Accum>and anAccumsand asserts they agree at every step, reading the packed side throughas_accumswith a scratch buffer that is never cleared by the test.Manual:
bin/environmentdwith 100k distinct groups andsumover fourintand threedoublecolumns.mz_arrangement_sizesforArrangeAccumulable [val: empty]:The 537 bytes saved per record is exactly seven 112-byte
Accums becoming four 24-byte and three 48-byte payloads plus a tag byte each (784 → 247); the rest of the record (key, time, offsets,Diff) is unchanged, and theReduceAccumulableoutput arrangement is byte-identical in both runs.Possible follow-ups, not in this PR:
is_zerodecodes a fullAccumper slot on the consolidation path and could read the counters directly if it shows up in benchmarks; the output arrangement is unchanged by this PR.🤖 Generated with Claude Code