Compact RowBinary array and map decoding stacks - #366
Conversation
|
Updated final design and benchmark notes after testing the broader RowBinary redesigns. Root causeThe old decoder materialized parser work from the collection length before reading collection values:
That made both eager decoding and streaming continuation state proportional to the declared collection length. A length prefix for one million values allocated a large type list even when no value bytes had arrived. Final implementationArrays now carry When all fixed-width bytes are available, the loop slices that exact chunk and decodes it without constructing parser operations. When a chunk ends partway through an array, the continuation stores one Maps now keep one This follows the useful parts of both references:
RowBinary has one important difference: the schema type is already known and constant for every array element. Keeping generated primitive loops lets BEAM select that parser once and avoids paying runtime type dispatch for every value. Rejected prototypesI implemented and measured a full reusable type-cycle state machine for rows, arrays, maps, and tuples, with I also tested one runtime-generic Final comparative benchmarkBenchee configuration: Apple M2, Elixir 1.20.2, OTP 29.0.3 JIT, clean dev compilation, 2 s warmup, 5 s run, and 2 s memory measurement.
The 10k result is a complete-payload benchmark: every run decodes all 10,000 element bytes and returns the materialized array. It is not the length-prefix-only continuation case. A short diagnostic run once made one PR build appear slower than an equivalent PR build; their executed hot path was identical and the run had high timing variance. The clean, longer comparison above is the relevant result. Absolute timing still varies with scheduler load, while the measured allocation difference was stable across runs. The wider 100k primitive-array run on the final code measured 591.90 us / 2.46 MB for UInt64, 608.92 us / 2.46 MB for Int64, 1.69 ms / 3.93 MB for Float64, and 6.38 ms / 14.32 MB for Date. Length-prefix-only continuation allocation remains constant-size: 224 bytes for arrays and 288 bytes for maps in the earlier one-million-element run, rather than growing with the declared length. The nanosecond timing in that microbenchmark is noisy; allocation shape is the meaningful result. Validation
|
03c6cdf to
b3924d8
Compare
b3924d8 to
6b960ef
Compare
Summary
Compact RowBinary array and map decoding so collection length no longer expands the type stack before values are read.
Fixed-width primitive arrays use generated counter loops sourced from the existing scalar decoder table, while parameterized arrays and nested collections use compact accumulator continuations.
Maps carry key/value types, remaining entries, and accumulated values in one frame instead of appending two type entries per map item.
Validated with formatting, 479 tests including focused byte-by-byte streaming cases, and comparative Benchee runs against current
master.