Repository navigation
perf(arrow): specialize Boolean filtering - #11732
Rich-T-kid wants to merge 7 commits into
Conversation
ed74f33 to
17548a3
Compare
|
run benchmark coalesce_kernels |
|
🤖 Benchmark starting (GKE) | trigger Target: coalesce_kernels Sharding factor: 1 (1 workers). Comparing rich-T-kid/coalescer-boolean-take (17548a3) to 1357f25 (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
Results will be posted when all workers finish. File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Target: coalesce_kernels Comparing rich-T-kid/coalescer-boolean-take (17548a3) to 1357f25 (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
DetailsPer-runner informationcoalesce_kernels — shard 1/1Node: gk3-benchmark-cluster-nap-1hlpf16k-a718e4aa-plqr Instance: c4a-highmem-16 (12 vCPU / 65 GiB) uname: BENCH_COMMAND: cargo bench --features=arrow,async,test_common,experimental,object_store --bench coalesce_kernelsCPU Details (lscpu)Resource Usagebase (merge-base)
branch
File an issue against this benchmark runner |
17548a3 to
f114c3e
Compare
|
@mbutrovich you may be interested since u have context with #11174 |
f114c3e to
e59a5c0
Compare
|
run benchmark coalesce_kernels |
|
🤖 Benchmark starting (GKE) | trigger Target: coalesce_kernels Sharding factor: 1 (1 workers). Comparing rich-T-kid/coalescer-boolean-take (e59a5c0) to 1357f25 (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
Results will be posted when all workers finish. File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Target: coalesce_kernels Comparing rich-T-kid/coalescer-boolean-take (e59a5c0) to 1357f25 (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
DetailsPer-runner informationcoalesce_kernels — shard 1/1Node: gk3-benchmark-cluster-nap-8he663m0-8580e27c-845q Instance: c4a-highmem-16 (12 vCPU / 65 GiB) uname: BENCH_COMMAND: cargo bench --features=arrow,async,test_common,experimental,object_store --bench coalesce_kernelsCPU Details (lscpu)Resource Usagebase (merge-base)
branch
File an issue against this benchmark runner |
e59a5c0 to
43295ad
Compare
|
run benchmark coalesce_kernels |
|
🤖 Benchmark starting (GKE) | trigger Target: coalesce_kernels Sharding factor: 1 (1 workers). Comparing rich-T-kid/coalescer-boolean-take (0211b4c) to d58a0fc (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
Results will be posted when all workers finish. File an issue against this benchmark runner |
|
run benchmark coalesce_kernels |
|
🤖 Benchmark starting (GKE) | trigger Target: coalesce_kernels Sharding factor: 1 (1 workers). Comparing rich-T-kid/coalescer-boolean-take (0211b4c) to d58a0fc (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
Results will be posted when all workers finish. File an issue against this benchmark runner |
I can take a look today. |
|
run benchmark coalesce_kernels |
|
🤖 Benchmark starting (GKE) | trigger Target: coalesce_kernels Sharding factor: 1 (1 workers). Comparing rich-T-kid/coalescer-boolean-take (6fcfb9d) to 01a9917 (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
Results will be posted when all workers finish. File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Target: coalesce_kernels Comparing rich-T-kid/coalescer-boolean-take (0211b4c) to d58a0fc (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
DetailsPer-runner informationcoalesce_kernels — shard 1/1Node: gk3-benchmark-cluster-nap-11mn6c6n-356aa77f-x2d6 Instance: c4a-highmem-16 (12 vCPU / 65 GiB) uname: BENCH_COMMAND: cargo bench --features=arrow,async,test_common,experimental,object_store --bench coalesce_kernelsCPU Details (lscpu)Resource Usagebase (merge-base)
branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Target: coalesce_kernels Comparing rich-T-kid/coalescer-boolean-take (0211b4c) to d58a0fc (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
DetailsPer-runner informationcoalesce_kernels — shard 1/1Node: gk3-benchmark-cluster-nap-9uvij0h8-355ce73c-4lc9 Instance: c4a-highmem-16 (12 vCPU / 65 GiB) uname: BENCH_COMMAND: cargo bench --features=arrow,async,test_common,experimental,object_store --bench coalesce_kernelsCPU Details (lscpu)Resource Usagebase (merge-base)
branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Target: coalesce_kernels Comparing rich-T-kid/coalescer-boolean-take (6fcfb9d) to 01a9917 (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
DetailsPer-runner informationcoalesce_kernels — shard 1/1Node: gk3-benchmark-cluster-nap-11mn6c6n-b51db246-rwbz Instance: c4a-highmem-16 (12 vCPU / 65 GiB) uname: BENCH_COMMAND: cargo bench --features=arrow,async,test_common,experimental,object_store --bench coalesce_kernelsCPU Details (lscpu)Resource Usagebase (merge-base)
branch
File an issue against this benchmark runner |
mbutrovich
left a comment
There was a problem hiding this comment.
Thanks @Rich-T-kid! Removing the concat at the end of each Boolean batch looks like a clear win in the benchmark runs above.
|
@mbutrovich asked AI to run some benchmarks these are the results. for context i'm on a macbook m4
we could add a couple for now ill include it but if it seems wonky id be happy to remove it |
|
Thanks for the updated numbers @Rich-T-kid. I don't love overspecializing implementation magic numbers on point-in-time benchmarks, unless it's something obvious like word size. Let me step back and think if there's a way to accomplish this without the selectivity heuristic, since that's only one signal on the distribution. |
|
run benchmark coalesce_kernels |
|
🤖 Benchmark starting (GKE) | trigger Target: coalesce_kernels Sharding factor: 1 (1 workers). Comparing rich-T-kid/coalescer-boolean-take (4c23338) to 01a9917 (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
Results will be posted when all workers finish. File an issue against this benchmark runner |
|
run benchmark coalesce_kernels |
|
🤖 Benchmark starting (GKE) | trigger Target: coalesce_kernels Sharding factor: 1 (1 workers). Comparing rich-T-kid/coalescer-boolean-take (4c23338) to 01a9917 (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
Results will be posted when all workers finish. File an issue against this benchmark runner |
|
What about replacing the per-bit path and both cutoffs with a kernel that filters values and validity straight into the builders, 64 bits at a time? As I read it, the per-bit path wins because of what it skips, not because appending one bit at a time is cheap. On the kernel path, Both cutoffs also depend on inputs the benchmark holds fixed:
The fused loop would look roughly like this, plus the trailing partial word: for (i, m) in filter_words.enumerate() {
if m == 0 {
continue;
}
let k = m.count_ones() as usize;
values.append_word(compress(value_words.chunk(i), m), k);
if let Some(validity) = &validity_words {
nulls.append_word(compress(validity.chunk(i), m), k);
}
}This needs no intermediate array or copy, and it scans the filter once for values and validity together. Its cost is per filter word, with no branch on the values. For precomputed
Would you be open to trying this in place of the per-bit path? If the prototype holds up on both x86_64 and aarch64, |
|
🤖 Benchmark completed (GKE) | trigger Target: coalesce_kernels Comparing rich-T-kid/coalescer-boolean-take (4c23338) to 01a9917 (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
DetailsPer-runner informationcoalesce_kernels — shard 1/1Node: gk3-benchmark-cluster-nap-2fege4dq-b5d3eead-zpss Instance: c4a-highmem-16 (12 vCPU / 65 GiB) uname: BENCH_COMMAND: cargo bench --features=arrow,async,test_common,experimental,object_store --bench coalesce_kernelsCPU Details (lscpu)Resource Usagebase (merge-base)
branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Target: coalesce_kernels Comparing rich-T-kid/coalescer-boolean-take (4c23338) to 01a9917 (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
DetailsPer-runner informationcoalesce_kernels — shard 1/1Node: gk3-benchmark-cluster-nap-1jz5nqdi-de95f2a3-wthn Instance: c4a-highmem-16 (12 vCPU / 65 GiB) uname: BENCH_COMMAND: cargo bench --features=arrow,async,test_common,experimental,object_store --bench coalesce_kernelsCPU Details (lscpu)Resource Usagebase (merge-base)
branch
File an issue against this benchmark runner |
|
@mbutrovich That makes sense, though it seems like a larger departure from the current approach. What do you think about dropping the additional thresholds from the latest commit and merging the simpler version first? We could then add the new NullBufferBuilder method in a follow-up before exploring the word-at-a-time optimization. |
I don't think we need to rush this and would rather get the design right. I'm also not sure the per-bit path is the simpler version, with one cutoff or two. Each cutoff is tuned to the benchmark inputs I listed above, and dropping the second one brings back the 0.005 regression from my runs. I'd rather not merge a cutoff that the next PR removes. If you'd like to land something first, what about the version with no cutoff at all, where |
|
Always use the existing filter kernel and append its output into the Boolean builders. This keeps the final concat avoidance without benchmark-specific sparse cutoffs.
|
run benchmark coalesce_kernels |
|
🤖 Benchmark starting (GKE) | trigger Target: coalesce_kernels Sharding factor: 1 (1 workers). Comparing rich-T-kid/coalescer-boolean-take (b4ddd37) to df4dc3e (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
Results will be posted when all workers finish. File an issue against this benchmark runner |
|
run benchmark coalesce_kernels |
|
🤖 Benchmark starting (GKE) | trigger Target: coalesce_kernels Sharding factor: 1 (1 workers). Comparing rich-T-kid/coalescer-boolean-take (b4ddd37) to df4dc3e (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
Results will be posted when all workers finish. File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Target: coalesce_kernels Comparing rich-T-kid/coalescer-boolean-take (b4ddd37) to df4dc3e (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
DetailsPer-runner informationcoalesce_kernels — shard 1/1Node: gk3-benchmark-cluster-nap-164vnrqr-001ae743-l956 Instance: c4a-highmem-16 (12 vCPU / 65 GiB) uname: BENCH_COMMAND: cargo bench --features=arrow,async,test_common,experimental,object_store --bench coalesce_kernelsCPU Details (lscpu)Resource Usagebase (merge-base)
branch
File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Target: coalesce_kernels Comparing rich-T-kid/coalescer-boolean-take (b4ddd37) to df4dc3e (merge-base) diff Run configurationrun benchmark coalesce_kernels
env:
BENCH_FILTER: single_boolean
shards: 1
DetailsPer-runner informationcoalesce_kernels — shard 1/1Node: gk3-benchmark-cluster-nap-1poo5pys-97e0df26-vphg Instance: c4a-highmem-16 (12 vCPU / 65 GiB) uname: BENCH_COMMAND: cargo bench --features=arrow,async,test_common,experimental,object_store --bench coalesce_kernelsCPU Details (lscpu)Resource Usagebase (merge-base)
branch
File an issue against this benchmark runner |
|
nice, results look good. Created #11867 as a follow up. results also look good there |
## Why Exercise the sparse filter path around its 1/128 selectivity cutoff. This follows up on apache#11732 (comment). ## What changed Added 0.005 selectivity to the coalesce filter benchmark matrix.
mbutrovich
left a comment
There was a problem hiding this comment.
Thanks @Rich-T-kid for taking the simpler route. Happy to review the followup PRs!
# Which issue does this PR close? N/A. Follow-up to apache#11732. # Rationale for this change Callers with packed validity bits currently need to append them one bit at a time or first build a `NullBuffer`. # What changes are included in this PR? This adds `NullBufferBuilder::append_word`, matching `BooleanBufferBuilder::append_word` while preserving lazy allocation for all-valid words. It also adds focused unit tests and benchmarks for different validity patterns. # Are these changes tested? The tests cover full and partial all-valid words without materialization, plus a mixed partial word at an unaligned offset and an all-valid append after materialization. The benchmark covers all-valid, very sparse, sparse, densely packed, and alternating validity words. # Are there any significant user-facing changes? Adds `NullBufferBuilder::append_word`.


Which issue does this PR close?
Rationale for this change
Boolean columns currently use
GenericInProgressArray, which buffers filtered arrays and concatenates them when the output batch is finished.What changes are included in this PR?
This adds
InProgressBooleanArray. It uses the existing Boolean filter kernel and appends the result directly into value and validity builders, avoiding the final concatenation.Are these changes tested?
The tests cover sliced and filtered copies, null propagation, and buffer reuse after
finish.Are there any significant user-facing changes?
No.