Skip to content

Bench: Cover Boolean filtering and oversized dictionaries - #12054

Merged
Rich-T-kid merged 6 commits into
apache:mainfrom
Rich-T-kid:rich-T-kid/dictionary-concat-ratio-bench
Nov 4, 2026
Merged

Rich-T-kid merged 6 commits into
apache:mainfrom
Rich-T-kid:rich-T-kid/dictionary-concat-ratio-bench

Conversation

@Rich-T-kid

@Rich-T-kid Rich-T-kid commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Part of #11885. Adds benchmark coverage for #11901 and #12008, and follows up on the Boolean filter coverage requested in #11918.

This consolidates and supersedes #11980 and #12041.

Why is this change needed?

The existing suites do not cover Boolean arrays as filter data or the cost of operating on small key ranges backed by oversized dictionaries. They also do not exercise the 16-values-per-selected-key boundary between dense and sparse dictionary mappings.

What changes are included in this PR?

Are these changes tested?

Built and ran each filtered benchmark locally.

Benchmark bot runs:

Are there any significant user-facing changes?

No. This only adds benchmark coverage.

@Rich-T-kid Rich-T-kid changed the title Bench: Add dictionary concat ratio sweep Bench: Cover Boolean filtering and oversized dictionaries Nov 3, 2026
@github-actions github-actions Bot added the arrow-core Core crates related to representing arrow data label Nov 3, 2026
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark filter_kernels
env:
BENCH_FILTER: "filter boolean"

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark row_format
env:
BENCH_FILTER: "convert_columns_prepared 8192 string_dictionary"

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark starting (GKE) | trigger

Target: row_format

Sharding factor: 1 (1 workers).

Comparing rich-T-kid/dictionary-concat-ratio-bench (37da834) to b231cf1 (merge-base) diff

Run configuration
run benchmark row_format
env:
  BENCH_FILTER: convert_columns_prepared 8192 string_dictionary
shards: 1

Results will be posted when all workers finish.


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark starting (GKE) | trigger

Target: filter_kernels

Sharding factor: 1 (1 workers).

Comparing rich-T-kid/dictionary-concat-ratio-bench (37da834) to b231cf1 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: filter boolean
shards: 1

Results will be posted when all workers finish.


File an issue against this benchmark runner

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

run benchmark concatenate_kernel
env:
BENCH_FILTER: "concat str_dict_slice"

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark starting (GKE) | trigger

Target: concatenate_kernel

Sharding factor: 1 (1 workers).

Comparing rich-T-kid/dictionary-concat-ratio-bench (37da834) to b231cf1 (merge-base) diff

Run configuration
run benchmark concatenate_kernel
env:
  BENCH_FILTER: concat str_dict_slice
shards: 1

Results will be posted when all workers finish.


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Target: row_format

Comparing rich-T-kid/dictionary-concat-ratio-bench (37da834) to b231cf1 (merge-base) diff

Run configuration
run benchmark row_format
env:
  BENCH_FILTER: convert_columns_prepared 8192 string_dictionary
shards: 1
Details
group                                                              changed
-----                                                              -------
convert_columns_prepared 8192 string_dictionary(1000000 values)    1.00      6.8±0.05ms        ? ?/sec
convert_columns_prepared 8192 string_dictionary(4000000 values)    1.00     67.9±0.43ms        ? ?/sec

Per-runner information
row_format — shard 1/1

Node: gk3-benchmark-cluster-nap-1q2slmnr-b33373f7-5pzx

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

uname:

Linux bench-c6608167940-4309-zqbz7 6.12.105+ #1 SMP Sat Sep 19 07:55:25 UTC 2026 aarch64 GNU/Linux

BENCH_COMMAND:

cargo bench --features=arrow,async,test_common,experimental,object_store --bench row_format
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Resource Usage

base (merge-base)

Metric Value
Wall time 5.0s
Peak memory 5.8 MiB
Avg memory 2.9 MiB
CPU user 0.0s
CPU sys 0.0s
Peak spill 0 B

branch

Metric Value
Wall time 25.0s
Peak memory 221.7 MiB
Avg memory 92.1 MiB
CPU user 13.5s
CPU sys 6.5s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Target: concatenate_kernel

Comparing rich-T-kid/dictionary-concat-ratio-bench (37da834) to b231cf1 (merge-base) diff

Run configuration
run benchmark concatenate_kernel
env:
  BENCH_FILTER: concat str_dict_slice
shards: 1
Details
group                                                        changed
-----                                                        -------
concat str_dict_slice rows=682 values_per_key=12 count=12    1.00    203.3±0.83µs        ? ?/sec
concat str_dict_slice rows=682 values_per_key=16 count=12    1.00    209.2±0.91µs        ? ?/sec
concat str_dict_slice rows=682 values_per_key=24 count=12    1.00    222.6±1.17µs        ? ?/sec

Per-runner information
concatenate_kernel — shard 1/1

Node: gk3-benchmark-cluster-nap-35k6403m-e6737b58-4fjg

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

uname:

Linux bench-c6608170975-4311-mb2tg 6.12.105+ #1 SMP Sat Sep 19 07:55:25 UTC 2026 aarch64 GNU/Linux

BENCH_COMMAND:

cargo bench --features=arrow,async,test_common,experimental,object_store --bench concatenate_kernel
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Resource Usage

base (merge-base)

Metric Value
Wall time 5.0s
Peak memory 129.6 MiB
Avg memory 38.9 MiB
CPU user 1.6s
CPU sys 0.1s
Peak spill 0 B

branch

Metric Value
Wall time 30.0s
Peak memory 130.7 MiB
Avg memory 94.3 MiB
CPU user 28.0s
CPU sys 0.1s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Target: filter_kernels

Comparing rich-T-kid/dictionary-concat-ratio-bench (37da834) to b231cf1 (merge-base) diff

Run configuration
run benchmark filter_kernels
env:
  BENCH_FILTER: filter boolean
shards: 1
Details
group                                                       changed
-----                                                       -------
filter boolean (kept 1/2)                                   1.00      8.6±0.00µs        ? ?/sec
filter boolean high selectivity (kept 1023/1024)            1.00      2.5±0.00µs        ? ?/sec
filter boolean low selectivity (kept 1/1024)                1.00    683.2±1.19ns        ? ?/sec
filter boolean w NULLs (kept 1/2)                           1.00     17.1±0.01µs        ? ?/sec
filter boolean w NULLs high selectivity (kept 1023/1024)    1.00      4.8±0.01µs        ? ?/sec
filter boolean w NULLs low selectivity (kept 1/1024)        1.00   1187.8±1.19ns        ? ?/sec

Per-runner information
filter_kernels — shard 1/1

Node: gk3-benchmark-cluster-nap-1q2slmnr-f9d718d9-5nfz

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

uname:

Linux bench-c6608167474-4310-lchtl 6.12.105+ #1 SMP Sat Sep 19 07:55:25 UTC 2026 aarch64 GNU/Linux

BENCH_COMMAND:

cargo bench --features=arrow,async,test_common,experimental,object_store --bench filter_kernels
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Resource Usage

base (merge-base)

Metric Value
Wall time 5.0s
Peak memory 4.9 MiB
Avg memory 829.3 KiB
CPU user 0.0s
CPU sys 0.0s
Peak spill 0 B

branch

Metric Value
Wall time 65.0s
Peak memory 27.7 MiB
Avg memory 25.0 MiB
CPU user 60.1s
CPU sys 0.0s
Peak spill 0 B

File an issue against this benchmark runner

@Rich-T-kid Rich-T-kid added the development-process Internal changes; PRs with this label are excluded from changelog label Nov 3, 2026
@github-actions github-actions Bot removed the development-process Internal changes; PRs with this label are excluded from changelog label Nov 3, 2026
@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

@etseidl could you take a look, just benchmarks and I double checked that they run

@etseidl
etseidl self-requested a review November 4, 2026 00:19
@etseidl

etseidl commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Sure, but it will be later this evening.

@etseidl etseidl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvm, it was smaller than I thought. 😅

@Rich-T-kid

Copy link
Copy Markdown
Contributor Author

thanks @etseidl

@Rich-T-kid
Rich-T-kid added this pull request to the merge queue Nov 4, 2026
Merged via the queue into apache:main with commit 258b0d2 Nov 4, 2026
21 checks passed
@Jefffrey Jefffrey added the development-process Internal changes; PRs with this label are excluded from changelog label Nov 4, 2026
lukekim pushed a commit to spiceai/arrow-rs that referenced this pull request Nov 5, 2026
…2043)

## Why

Closes apache#11901.

The benchmark coverage for this change is in the consolidated benchmark
PR apache#12054.

`RowConverter` currently encodes every value in a dictionary before
converting a batch. For an 8,192-row slice backed by a large dictionary,
the work therefore scales with the retained values array rather than the
rows being converted.

```text
DictionaryArray
  keys:     8,192 rows --------+
                               +--> RowConverter --> 8,192 encoded rows
  values: 1,000,000 strings ---+

before: encode all 1,000,000 values --> look up 8,192 keys
after:  gather 8,192 row values      --> encode the row-aligned values
```

This is a smaller follow-up to apache#11904. It avoids the hash map and
per-row remap used there, and uses the benchmark crossover requested in
[review](apache#11904 (comment))
to keep small and dense dictionaries on the existing path.

## What changed

When a dictionary has at least 32K values and its values array is at
least four times longer than the input, gather one value per row and
encode that row-aligned array. All other dictionaries keep the existing
dictionary-key lookup path.

The benchmark sweep put the crossover between 20K and 24.6K values. The
32K / 4x guard is deliberately conservative.

## Results

The [hosted exact-ref
run](apache#12043 (comment))
is flat at 10K, 1.3x faster at 32K, 3.45x at 100K, and about 33x at 1M.
Row decoding is unchanged, and peak memory drops from 56.8 MiB to 29.5
MiB.

## Test coverage

One test checks row equality and round-trip decoding for a sliced
dictionary with repeated high keys and nulls.
ZENOTME pushed a commit to ZENOTME/arrow-rs that referenced this pull request Nov 8, 2026
# Which issue does this PR close?

Closes apache#11885

# Why is this change needed?

Slicing a dictionary array only slices its keys; it still retains the
full values array. When concat or interleave merges short slices from
large dictionaries, the current code builds a mask and key mapping for
every dictionary value, including values that no selected row
references. The work therefore scales with the full dictionary instead
of the number of output rows.

```text
Example: one 682-row slice backed by 1,000,000 dictionary values

slice keys:       [900_000, 17, 900_000, ...]   682 entries
                       |       |
                       v       v
retained values: [v0, ... v17, ... v900_000, ...]   1,000,000 entries

                 old                              new
                  |                                |
     allocate and scan by values.len()    scan the selected keys
                  |                                |
     1,000,000-slot referenced mask       sort + deduplicate
     1,000,000-slot key mapping           {17, 900_000, ...}
                  |                                |
                  +---------------+----------------+
                                  v
             same merged dictionary for concat / interleave
```

# What changes are included in this PR?

When a dictionary has more than 256 values per key lookup, collect only
the referenced keys and store the old-to-new key mappings sparsely. The
gate uses the number of remaps performed by concat or interleave, rather
than the number of distinct selected rows, so mostly-null and repeatedly
selected inputs stay on the dense path when hashing would be slower.

Let `r` be the number of selected rows, `l` the number of key lookups,
`u` the number of unique referenced keys, and `v` the number of retained
dictionary values. The sparse path collects keys in `O(r)`, sorts them
in `O(r log r)`, deduplicates them in `O(r)`, builds the hash mapping in
`O(u)` expected time, and remaps the output in `O(l)` expected time.

This replaces the dense path's `O(v)` mask and mapping work. Sorting
also makes repeated keys adjacent and preserves the ascending
dictionary-key order produced by the dense mask path, so both paths
build the merged dictionary in the same order. Both concat and
interleave use this shared dictionary merge path. BatchCoalescer has a
separate dictionary merge path and is intentionally left for a
follow-up.

# Are these changes tested?

Tests cover concat and interleave of short slices backed by large
dictionaries, the key-lookup gate, sparse primitive dictionaries, null
dictionary values, null keys, `UInt8` key overflow, and the existing
dense path.

The benchmark now covers ratios of 12, 16, 24, 128, 256, 400, and 1024
values per key with both contiguous and scattered keys. [Review
measurements](apache#12008 (comment))
placed the scattered-key crossover near 256:1: at 400:1 the measured
head/base ratios were 0.57-0.84, and at 1024:1 they were 0.35-0.56. The
earlier [hosted
run](apache#12008 (comment))
improved the 12:1, 16:1, and 24:1 dense-path cases by 10.0%, 8.5%, and
8.7%. The benchmark harness was introduced in apache#12054.

# Are there any significant user-facing changes?

No. This only changes how dictionary values are merged internally.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arrow-core Core crates related to representing arrow data development-process Internal changes; PRs with this label are excluded from changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants