Skip to content

perf: Faster grouped first_value / last_value with a single primitive ORDER BY key - #27303

Open
freakyzoidberg wants to merge 6 commits into
apache:mainfrom
freakyzoidberg:perf/first-last-primitive-key
Open

freakyzoidberg wants to merge 6 commits into
apache:mainfrom
freakyzoidberg:perf/first-last-primitive-key

Conversation

@freakyzoidberg

@freakyzoidberg freakyzoidberg commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

Grouped first_value / last_value with ORDER BY keep each group's ordering as a Vec<ScalarValue>. Every batch winner is converted to ScalarValues and compared with compare_rows, and every batch scans a scoreboard sized to all groups. With many groups, this dominates the query.

What changes are included in this PR?

  • Add FirstLastPrimitiveKeyGroupsAccumulator<K>, used when there is exactly one ORDER BY expression of primitive type. It is picked with downcast_primitive!, like GroupValuesPrimitive for GROUP BY keys. It keeps the orderings in a Vec<K::Native> plus a null bitmap and compares them directly, in one pass per batch.
  • Add ValueState::update_rows, so the value state is updated once per batch.
  • The partial state ([value, ordering, is_set]) and the rows picked, ties included, are the same as with the existing accumulator, which still handles every other ordering.

What is the testing strategy for this PR?

  • first_last_primitive_key.slt compares ORDER BY k with ORDER BY k, u. u is unique, so the existing accumulator is the oracle. It covers:
    • key types with their own comparison: Int8, UInt64, Float16 / Float64 with NaN and -0.0, Decimal128 / 256, Timestamp, Interval;
    • every ASC / DESC × NULLS combination;
    • IGNORE NULLS, FILTER, and skip partial aggregation.
  • Unit tests for the routing and for ties.
  • Existing single-key tests (first_last_ordered.slt, group_by.slt, aggregate.slt) now run on the new path unchanged.

Benchmarks

cargo bench on an M4 Max, main vs this PR:

bench main this PR
first_value update_bench nulls=0%, filter=false 24.1 ms 7.9 ms
last_value update_bench nulls=0%, filter=true 37.2 ms 4.6 ms
last_value merge_bench nulls=0%, filter=false 29.2 ms 7.5 ms
first_value update_bench struct(i64,utf8,f64) nulls=0% 25.8 ms 9.6 ms
first_value merge_bench map<utf8,i64> nulls=0% 30.9 ms 9.8 ms
first_value coalesce_peers(i64,utf8,f64) separate x3 (winner changes) 62.1 ms 38.2 ms
first_value evaluate_bench nulls=0%, filter=false, first(2) 11.0 µs 0.69 µs

Are there any user-facing changes?

No.

AI usage

Written with the help of AI tools
I've reviewed and understand the code.

freakyzoidberg and others added 4 commits November 10, 2026 09:36
…imitive ORDER BY key

With a single ORDER BY expression of primitive type, grouped first_value
and last_value now keep each group's ordering as a native value and
compare it directly in one pass per batch, instead of ScalarValue
orderings. The value state gets a batched update_rows.

Results are unchanged: ties keep the first row, pre-ordered input keeps
the first or last row, and the partial state is [value, ordering, is_set].
Tests compare the typed path with the generic one (ORDER BY k vs
ORDER BY k, u, and on tied keys) and keep two-column twins of the tests
it takes over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A fresh accumulator grew it by doubling during its first batch. The freed blocks then scattered the per-group nested values allocated right after, which made evaluate for nested values 5-10% slower in benches/first_last.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Single-key queries there now run on the typed accumulator. The generic accumulator keeps its unit tests, and the clustered memory test orders by two columns to stay on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…omparison

Keep Int8, UInt64, Float16, Float64, Decimal128, Decimal256, a timezone-aware Timestamp and Interval(MonthDayNano), and drop the array_agg check of the generated data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) functions Changes to functions implementation physical-plan Changes to the physical-plan crate labels Nov 10, 2026
@gabotechs

Copy link
Copy Markdown
Contributor

run benchmark first_last

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c6769146201-4745-t7pjl 6.12.105+ #1 SMP Sat Sep 19 07:55:25 UTC 2026 aarch64 GNU/Linux

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

Comparing perf/first-last-primitive-key (edf2a3c) to 9e0a08f (merge-base) diff

Run configuration
run benchmark first_last

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing perf/first-last-primitive-key (edf2a3c) to 9e0a08f (merge-base) diff

Run configuration
run benchmark first_last
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
Details

group                                                                         HEAD                                   perf_first-last-primitive-key
-----                                                                         ----                                   -----------------------------
first_value coalesce_peers(i64,utf8,f64) coalesced struct (winner changes)    1.00    437.5±1.52ms        ? ?/sec    1.01    443.7±2.17ms        ? ?/sec
first_value coalesce_peers(i64,utf8,f64) coalesced struct (winner stable)     1.86     35.0±0.10ms        ? ?/sec    1.00     18.8±0.15ms        ? ?/sec
first_value coalesce_peers(i64,utf8,f64) separate x3 (winner changes)         1.00     79.3±1.58ms        ? ?/sec    1.12     88.4±1.49ms        ? ?/sec
first_value coalesce_peers(i64,utf8,f64) separate x3 (winner stable)          2.19     89.9±0.27ms        ? ?/sec    1.00     41.1±0.84ms        ? ?/sec
first_value evaluate_bench list<i64>[4] nulls=0%, all                         1.00   494.5±28.41µs        ? ?/sec    1.04   512.2±24.53µs        ? ?/sec
first_value evaluate_bench list<i64>[4] nulls=90%, all                        1.00    558.4±6.13µs        ? ?/sec    1.08    600.9±1.93µs        ? ?/sec
first_value evaluate_bench list<struct(i64,utf8)>[4] nulls=0%, all            1.00    793.2±4.29µs        ? ?/sec    1.07    846.8±5.44µs        ? ?/sec
first_value evaluate_bench list<struct(i64,utf8)>[4] nulls=90%, all           1.00   826.5±22.33µs        ? ?/sec    1.09    898.5±3.59µs        ? ?/sec
first_value evaluate_bench map<utf8,i64> nulls=0%, all                        1.00    669.0±3.94µs        ? ?/sec    1.00    669.2±2.87µs        ? ?/sec
first_value evaluate_bench map<utf8,i64> nulls=90%, all                       1.00   736.0±25.57µs        ? ?/sec    1.06    779.0±4.08µs        ? ?/sec
first_value evaluate_bench nulls=0%, filter=false, all                        1.00     18.0±1.55µs        ? ?/sec    1.17   21.1±203.23µs        ? ?/sec
first_value evaluate_bench nulls=0%, filter=false, first(2)                   1.00     14.2±1.38µs        ? ?/sec    1.67   23.7±212.66µs        ? ?/sec
first_value evaluate_bench nulls=0%, filter=true, all                         1.66     19.0±2.88µs        ? ?/sec    1.00   11.4±102.68µs        ? ?/sec
first_value evaluate_bench nulls=0%, filter=true, first(2)                    1.58     19.7±2.09µs        ? ?/sec    1.00   12.5±104.41µs        ? ?/sec
first_value evaluate_bench nulls=90%, filter=false, all                       1.75     17.5±1.72µs        ? ?/sec    1.00    10.0±93.32µs        ? ?/sec
first_value evaluate_bench nulls=90%, filter=false, first(2)                  1.19     18.8±1.75µs        ? ?/sec    1.00   15.8±141.98µs        ? ?/sec
first_value evaluate_bench nulls=90%, filter=true, all                        2.72     17.2±1.80µs        ? ?/sec    1.00     6.3±56.13µs        ? ?/sec
first_value evaluate_bench nulls=90%, filter=true, first(2)                   2.69     19.1±2.47µs        ? ?/sec    1.00     7.1±54.71µs        ? ?/sec
first_value evaluate_bench struct(i64,utf8,f64) nulls=0%, all                 1.00    717.6±2.19µs        ? ?/sec    1.05    754.4±3.55µs        ? ?/sec
first_value evaluate_bench struct(i64,utf8,f64) nulls=90%, all                1.00  1058.8±39.25µs        ? ?/sec    1.12   1184.3±4.84µs        ? ?/sec
first_value merge_bench list<i64>[4] nulls=0%                                 2.34     37.9±0.10ms        ? ?/sec    1.00     16.2±0.08ms        ? ?/sec
first_value merge_bench list<i64>[4] nulls=90%                                2.31     38.2±0.13ms        ? ?/sec    1.00     16.6±0.11ms        ? ?/sec
first_value merge_bench list<struct(i64,utf8)>[4] nulls=0%                    2.10     40.7±0.09ms        ? ?/sec    1.00     19.3±0.14ms        ? ?/sec
first_value merge_bench list<struct(i64,utf8)>[4] nulls=90%                   2.09     41.3±0.13ms        ? ?/sec    1.00     19.8±0.11ms        ? ?/sec
first_value merge_bench map<utf8,i64> nulls=0%                                2.15     40.2±0.10ms        ? ?/sec    1.00     18.7±0.11ms        ? ?/sec
first_value merge_bench map<utf8,i64> nulls=90%                               2.07     41.8±0.13ms        ? ?/sec    1.00     20.2±0.14ms        ? ?/sec
first_value merge_bench nulls=0%, filter=false                                2.64     35.5±0.08ms        ? ?/sec    1.00     13.5±0.01ms        ? ?/sec
first_value merge_bench nulls=0%, filter=true                                 2.54     35.6±0.05ms        ? ?/sec    1.00     14.0±0.03ms        ? ?/sec
first_value merge_bench nulls=90%, filter=false                               2.27     38.0±0.06ms        ? ?/sec    1.00     16.7±0.01ms        ? ?/sec
first_value merge_bench nulls=90%, filter=true                                2.33     38.6±0.49ms        ? ?/sec    1.00     16.6±0.04ms        ? ?/sec
first_value merge_bench struct(i64,utf8,f64) nulls=0%                         2.16     40.2±0.15ms        ? ?/sec    1.00     18.6±0.14ms        ? ?/sec
first_value merge_bench struct(i64,utf8,f64) nulls=90%                        1.99     42.3±0.16ms        ? ?/sec    1.00     21.3±0.25ms        ? ?/sec
first_value trivial_update_bench nulls=0%, ignore_nulls=false                 1.05      2.7±0.06µs        ? ?/sec    1.00      2.5±0.05µs        ? ?/sec
first_value trivial_update_bench nulls=0%, ignore_nulls=true                  1.04      2.7±0.09µs        ? ?/sec    1.00      2.6±0.04µs        ? ?/sec
first_value trivial_update_bench nulls=90%, ignore_nulls=false                1.03      3.0±0.20µs        ? ?/sec    1.00      2.9±0.16µs        ? ?/sec
first_value trivial_update_bench nulls=90%, ignore_nulls=true                 1.01      3.6±0.03µs        ? ?/sec    1.00      3.6±0.02µs        ? ?/sec
first_value update_bench list<i64>[4] nulls=0%                                2.02     32.5±0.12ms        ? ?/sec    1.00     16.1±0.10ms        ? ?/sec
first_value update_bench list<i64>[4] nulls=90%                               2.00     33.2±0.08ms        ? ?/sec    1.00     16.6±0.15ms        ? ?/sec
first_value update_bench list<struct(i64,utf8)>[4] nulls=0%                   1.84     35.7±0.16ms        ? ?/sec    1.00     19.5±0.11ms        ? ?/sec
first_value update_bench list<struct(i64,utf8)>[4] nulls=90%                  1.84     36.4±0.11ms        ? ?/sec    1.00     19.8±0.14ms        ? ?/sec
first_value update_bench map<utf8,i64> nulls=0%                               1.88     35.3±0.09ms        ? ?/sec    1.00     18.7±0.11ms        ? ?/sec
first_value update_bench map<utf8,i64> nulls=90%                              1.83     36.8±0.08ms        ? ?/sec    1.00     20.1±0.12ms        ? ?/sec
first_value update_bench nulls=0%, filter=false                               2.25     30.2±0.38ms        ? ?/sec    1.00     13.4±0.01ms        ? ?/sec
first_value update_bench nulls=0%, filter=true                                5.76     47.0±0.16ms        ? ?/sec    1.00      8.2±0.01ms        ? ?/sec
first_value update_bench nulls=90%, filter=false                              2.07     34.1±0.23ms        ? ?/sec    1.00     16.5±0.02ms        ? ?/sec
first_value update_bench nulls=90%, filter=true                               4.69     46.4±0.12ms        ? ?/sec    1.00      9.9±0.02ms        ? ?/sec
first_value update_bench struct(i64,utf8,f64) nulls=0%                        1.87     35.2±0.09ms        ? ?/sec    1.00     18.8±0.14ms        ? ?/sec
first_value update_bench struct(i64,utf8,f64) nulls=90%                       1.79     37.6±0.14ms        ? ?/sec    1.00     21.0±0.17ms        ? ?/sec
last_value evaluate_bench nulls=0%, filter=false, all                         1.00     18.3±2.14µs        ? ?/sec    1.21   22.1±213.35µs        ? ?/sec
last_value evaluate_bench nulls=0%, filter=false, first(2)                    1.02     19.9±2.46µs        ? ?/sec    1.00   19.5±177.48µs        ? ?/sec
last_value evaluate_bench nulls=0%, filter=true, all                          1.90     17.7±2.17µs        ? ?/sec    1.00     9.3±85.22µs        ? ?/sec
last_value evaluate_bench nulls=0%, filter=true, first(2)                     1.32     19.2±2.01µs        ? ?/sec    1.00   14.5±128.81µs        ? ?/sec
last_value evaluate_bench nulls=90%, filter=false, all                        1.93     17.8±2.16µs        ? ?/sec    1.00     9.3±85.08µs        ? ?/sec
last_value evaluate_bench nulls=90%, filter=false, first(2)                   1.26     19.4±2.36µs        ? ?/sec    1.00   15.5±137.55µs        ? ?/sec
last_value evaluate_bench nulls=90%, filter=true, all                         1.93     17.1±1.84µs        ? ?/sec    1.00     8.9±79.82µs        ? ?/sec
last_value evaluate_bench nulls=90%, filter=true, first(2)                    1.89     18.5±1.69µs        ? ?/sec    1.00     9.8±81.48µs        ? ?/sec
last_value merge_bench list<i64>[4] nulls=0%                                  2.35     37.9±0.10ms        ? ?/sec    1.00     16.1±0.10ms        ? ?/sec
last_value merge_bench list<i64>[4] nulls=90%                                 2.31     38.3±0.11ms        ? ?/sec    1.00     16.6±0.16ms        ? ?/sec
last_value merge_bench list<struct(i64,utf8)>[4] nulls=0%                     2.13     40.9±0.08ms        ? ?/sec    1.00     19.2±0.15ms        ? ?/sec
last_value merge_bench list<struct(i64,utf8)>[4] nulls=90%                    2.09     41.5±0.09ms        ? ?/sec    1.00     19.8±0.13ms        ? ?/sec
last_value merge_bench map<utf8,i64> nulls=0%                                 2.15     40.4±0.08ms        ? ?/sec    1.00     18.8±0.09ms        ? ?/sec
last_value merge_bench map<utf8,i64> nulls=90%                                2.08     42.4±0.09ms        ? ?/sec    1.00     20.4±0.13ms        ? ?/sec
last_value merge_bench nulls=0%, filter=false                                 2.68     36.0±0.06ms        ? ?/sec    1.00     13.4±0.00ms        ? ?/sec
last_value merge_bench nulls=0%, filter=true                                  2.64     36.0±0.13ms        ? ?/sec    1.00     13.6±0.24ms        ? ?/sec
last_value merge_bench nulls=90%, filter=false                                2.63     46.1±0.08ms        ? ?/sec    1.00     17.5±0.03ms        ? ?/sec
last_value merge_bench nulls=90%, filter=true                                 2.62     46.1±0.09ms        ? ?/sec    1.00     17.6±0.05ms        ? ?/sec
last_value merge_bench struct(i64,utf8,f64) nulls=0%                          2.17     40.4±0.10ms        ? ?/sec    1.00     18.6±0.12ms        ? ?/sec
last_value merge_bench struct(i64,utf8,f64) nulls=90%                         2.03     42.5±0.09ms        ? ?/sec    1.00     20.9±0.12ms        ? ?/sec
last_value trivial_update_bench nulls=0%, ignore_nulls=false                  1.01      2.7±0.11µs        ? ?/sec    1.00      2.6±0.12µs        ? ?/sec
last_value trivial_update_bench nulls=0%, ignore_nulls=true                   1.02      2.7±0.13µs        ? ?/sec    1.00      2.6±0.05µs        ? ?/sec
last_value trivial_update_bench nulls=90%, ignore_nulls=false                 1.03      3.0±0.03µs        ? ?/sec    1.00      2.9±0.03µs        ? ?/sec
last_value trivial_update_bench nulls=90%, ignore_nulls=true                  1.03      7.5±0.04µs        ? ?/sec    1.00      7.3±0.02µs        ? ?/sec
last_value update_bench list<i64>[4] nulls=0%                                 1.98     32.1±0.10ms        ? ?/sec    1.00     16.2±0.12ms        ? ?/sec
last_value update_bench list<i64>[4] nulls=90%                                1.99     32.7±0.15ms        ? ?/sec    1.00     16.5±0.12ms        ? ?/sec
last_value update_bench list<struct(i64,utf8)>[4] nulls=0%                    1.86     35.6±0.10ms        ? ?/sec    1.00     19.1±0.18ms        ? ?/sec
last_value update_bench list<struct(i64,utf8)>[4] nulls=90%                   1.82     36.0±0.08ms        ? ?/sec    1.00     19.8±0.17ms        ? ?/sec
last_value update_bench map<utf8,i64> nulls=0%                                1.89     35.2±0.11ms        ? ?/sec    1.00     18.6±0.08ms        ? ?/sec
last_value update_bench map<utf8,i64> nulls=90%                               1.85     37.1±0.08ms        ? ?/sec    1.00     20.1±0.12ms        ? ?/sec
last_value update_bench nulls=0%, filter=false                                2.30     31.1±0.07ms        ? ?/sec    1.00     13.5±0.13ms        ? ?/sec
last_value update_bench nulls=0%, filter=true                                 5.78     47.3±0.10ms        ? ?/sec    1.00      8.2±0.00ms        ? ?/sec
last_value update_bench nulls=90%, filter=false                               2.38     41.8±0.16ms        ? ?/sec    1.00     17.6±0.01ms        ? ?/sec
last_value update_bench nulls=90%, filter=true                                5.67     53.9±0.17ms        ? ?/sec    1.00      9.5±0.01ms        ? ?/sec
last_value update_bench struct(i64,utf8,f64) nulls=0%                         1.88     35.0±0.09ms        ? ?/sec    1.00     18.6±0.18ms        ? ?/sec
last_value update_bench struct(i64,utf8,f64) nulls=90%                        1.77     37.2±0.10ms        ? ?/sec    1.00     21.0±0.16ms        ? ?/sec

Resource Usage

first_last — base (merge-base)

Metric Value
Wall time 1180.3s
Peak memory 2.8 GiB
Avg memory 144.9 MiB
CPU user 1234.1s
CPU sys 9.8s
Peak spill 0 B

first_last — branch

Metric Value
Wall time 1005.2s
Peak memory 2.8 GiB
Avg memory 187.3 MiB
CPU user 1071.3s
CPU sys 11.3s
Peak spill 0 B

File an issue against this benchmark runner

@freakyzoidberg

freakyzoidberg commented Nov 10, 2026 •

Copy link
Copy Markdown
Member Author

Thanks,

Looking mostly good, with 2x to 5x accross,

digging a bit more in this one:

first_value coalesce_peers(i64,utf8,f64) separate x3 (winner changes)         1.00     79.3±1.58ms        ? ?/sec    1.12     88.4±1.49ms        ? ?/sec

the other "regression" appear to be noise (evaluate_bench with microsecs)

@gabotechs

Copy link
Copy Markdown
Contributor

Probably this one is also worth a look

first_value evaluate_bench nulls=0%, filter=false, first(2)                   1.00     14.2±1.38µs        ? ?/sec    1.67   23.7±212.66µs        ? ?/sec

When most rows beat their group's winner, recording every winning row and de-duplicating afterwards cost as much as it saved. Keep one entry per touched group instead, which also removes keep_last_update_of_each_group and ValueState::overwrite_is_cheap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@freakyzoidberg

Copy link
Copy Markdown
Member Author

Probably this one is also worth a look

first_value evaluate_bench nulls=0%, filter=false, first(2)                   1.00     14.2±1.38µs        ? ?/sec    1.67   23.7±212.66µs        ? ?/sec

Thanks, that one and the coalesce_peers (winner changes) should be fixed now.

Before the per-batch scratch buffer was sized to the batch input row count (1 MB) and freed inside the timed closure (evaluate_bench improvement), now its capped to the number of groups (updates on the same group key are done in place in a scratch, then merge in the state at the end of batch - (coalesce_peers improvement))

Could you re-run run benchmark first_last?

@jayzhan211 jayzhan211 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.

@freakyzoidberg one test change requested inline. I also benchmarked head against the merge-base on an M-series laptop: both regressions flagged above are gone (coalesce_peers separate x3 (winner changes) 54.8 → 42.4 ms, evaluate_bench ... first(2) 11.8 → 0.59 µs), and the other cases I ran are 60–80% faster.

@gabotechs

Copy link
Copy Markdown
Contributor

run benchmark first_last

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c6817175130-4818-rbspk 6.12.105+ #1 SMP Sat Sep 19 07:55:25 UTC 2026 aarch64 GNU/Linux

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

Comparing perf/first-last-primitive-key (4a70a2e) to 9e0a08f (merge-base) diff

Run configuration
run benchmark first_last

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

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

Comparing perf/first-last-primitive-key (4a70a2e) to 9e0a08f (merge-base) diff

Run configuration
run benchmark first_last
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
Details

group                                                                         HEAD                                   perf_first-last-primitive-key
-----                                                                         ----                                   -----------------------------
first_value coalesce_peers(i64,utf8,f64) coalesced struct (winner changes)    1.04   487.7±12.15ms        ? ?/sec    1.00    469.0±6.13ms        ? ?/sec
first_value coalesce_peers(i64,utf8,f64) coalesced struct (winner stable)     1.94     36.6±0.45ms        ? ?/sec    1.00     18.9±0.18ms        ? ?/sec
first_value coalesce_peers(i64,utf8,f64) separate x3 (winner changes)         1.11     80.1±1.26ms        ? ?/sec    1.00     72.0±0.57ms        ? ?/sec
first_value coalesce_peers(i64,utf8,f64) separate x3 (winner stable)          2.19     91.5±0.85ms        ? ?/sec    1.00     41.8±0.90ms        ? ?/sec
first_value evaluate_bench list<i64>[4] nulls=0%, all                         1.13   590.1±24.19µs        ? ?/sec    1.00   524.4±14.19µs        ? ?/sec
first_value evaluate_bench list<i64>[4] nulls=90%, all                        1.22   741.7±33.35µs        ? ?/sec    1.00   607.0±30.71µs        ? ?/sec
first_value evaluate_bench list<struct(i64,utf8)>[4] nulls=0%, all            1.07   926.6±42.55µs        ? ?/sec    1.00   863.4±23.62µs        ? ?/sec
first_value evaluate_bench list<struct(i64,utf8)>[4] nulls=90%, all           1.01   986.1±56.46µs        ? ?/sec    1.00   978.2±42.07µs        ? ?/sec
first_value evaluate_bench map<utf8,i64> nulls=0%, all                        1.06   785.8±30.15µs        ? ?/sec    1.00   743.2±24.67µs        ? ?/sec
first_value evaluate_bench map<utf8,i64> nulls=90%, all                       1.11   905.3±34.55µs        ? ?/sec    1.00   815.6±26.25µs        ? ?/sec
first_value evaluate_bench nulls=0%, filter=false, all                        1.96     21.7±2.73µs        ? ?/sec    1.00   11.1±100.30µs        ? ?/sec
first_value evaluate_bench nulls=0%, filter=false, first(2)                   1.30     18.4±2.13µs        ? ?/sec    1.00   14.1±120.04µs        ? ?/sec
first_value evaluate_bench nulls=0%, filter=true, all                         3.01     21.3±2.84µs        ? ?/sec    1.00     7.1±60.75µs        ? ?/sec
first_value evaluate_bench nulls=0%, filter=true, first(2)                    2.77     23.1±3.06µs        ? ?/sec    1.00     8.4±59.69µs        ? ?/sec
first_value evaluate_bench nulls=90%, filter=false, all                       1.61     19.3±2.36µs        ? ?/sec    1.00   12.0±110.04µs        ? ?/sec
first_value evaluate_bench nulls=90%, filter=false, first(2)                  1.87     23.3±3.23µs        ? ?/sec    1.00    12.4±99.84µs        ? ?/sec
first_value evaluate_bench nulls=90%, filter=true, all                        2.56     19.5±2.76µs        ? ?/sec    1.00     7.6±66.70µs        ? ?/sec
first_value evaluate_bench nulls=90%, filter=true, first(2)                   2.54     21.4±2.84µs        ? ?/sec    1.00     8.4±61.61µs        ? ?/sec
first_value evaluate_bench struct(i64,utf8,f64) nulls=0%, all                 1.11   848.9±28.08µs        ? ?/sec    1.00   762.1±24.87µs        ? ?/sec
first_value evaluate_bench struct(i64,utf8,f64) nulls=90%, all                1.02  1306.9±56.50µs        ? ?/sec    1.00  1283.8±63.26µs        ? ?/sec
first_value merge_bench list<i64>[4] nulls=0%                                 2.37     38.7±0.20ms        ? ?/sec    1.00     16.3±0.22ms        ? ?/sec
first_value merge_bench list<i64>[4] nulls=90%                                2.34     39.3±0.24ms        ? ?/sec    1.00     16.8±0.18ms        ? ?/sec
first_value merge_bench list<struct(i64,utf8)>[4] nulls=0%                    2.14     42.0±0.37ms        ? ?/sec    1.00     19.6±0.17ms        ? ?/sec
first_value merge_bench list<struct(i64,utf8)>[4] nulls=90%                   2.10     42.5±0.32ms        ? ?/sec    1.00     20.3±0.29ms        ? ?/sec
first_value merge_bench map<utf8,i64> nulls=0%                                2.18     41.3±0.34ms        ? ?/sec    1.00     18.9±0.18ms        ? ?/sec
first_value merge_bench map<utf8,i64> nulls=90%                               2.10     43.2±0.50ms        ? ?/sec    1.00     20.5±0.24ms        ? ?/sec
first_value merge_bench nulls=0%, filter=false                                2.64     36.2±0.18ms        ? ?/sec    1.00     13.7±0.25ms        ? ?/sec
first_value merge_bench nulls=0%, filter=true                                 2.62     36.1±0.14ms        ? ?/sec    1.00     13.8±0.21ms        ? ?/sec
first_value merge_bench nulls=90%, filter=false                               2.24     38.2±0.14ms        ? ?/sec    1.00     17.0±0.07ms        ? ?/sec
first_value merge_bench nulls=90%, filter=true                                2.25     38.3±0.19ms        ? ?/sec    1.00     17.0±0.09ms        ? ?/sec
first_value merge_bench struct(i64,utf8,f64) nulls=0%                         2.18     41.1±0.24ms        ? ?/sec    1.00     18.8±0.21ms        ? ?/sec
first_value merge_bench struct(i64,utf8,f64) nulls=90%                        2.07     44.1±0.37ms        ? ?/sec    1.00     21.3±0.30ms        ? ?/sec
first_value trivial_update_bench nulls=0%, ignore_nulls=false                 1.03      2.8±0.09µs        ? ?/sec    1.00      2.7±0.08µs        ? ?/sec
first_value trivial_update_bench nulls=0%, ignore_nulls=true                  1.02      2.8±0.08µs        ? ?/sec    1.00      2.7±0.09µs        ? ?/sec
first_value trivial_update_bench nulls=90%, ignore_nulls=false                1.01      3.0±0.29µs        ? ?/sec    1.00      3.0±0.18µs        ? ?/sec
first_value trivial_update_bench nulls=90%, ignore_nulls=true                 1.00      3.8±0.06µs        ? ?/sec    1.00      3.8±0.09µs        ? ?/sec
first_value update_bench list<i64>[4] nulls=0%                                2.09     33.8±0.29ms        ? ?/sec    1.00     16.2±0.25ms        ? ?/sec
first_value update_bench list<i64>[4] nulls=90%                               2.03     34.1±0.40ms        ? ?/sec    1.00     16.8±0.20ms        ? ?/sec
first_value update_bench list<struct(i64,utf8)>[4] nulls=0%                   1.90     37.0±0.28ms        ? ?/sec    1.00     19.5±0.20ms        ? ?/sec
first_value update_bench list<struct(i64,utf8)>[4] nulls=90%                  1.86     38.0±0.38ms        ? ?/sec    1.00     20.4±0.87ms        ? ?/sec
first_value update_bench map<utf8,i64> nulls=0%                               1.90     36.3±0.30ms        ? ?/sec    1.00     19.1±0.55ms        ? ?/sec
first_value update_bench map<utf8,i64> nulls=90%                              1.87     38.1±0.58ms        ? ?/sec    1.00     20.4±0.22ms        ? ?/sec
first_value update_bench nulls=0%, filter=false                               2.20     30.7±0.25ms        ? ?/sec    1.00     14.0±0.39ms        ? ?/sec
first_value update_bench nulls=0%, filter=true                                5.50     47.6±0.22ms        ? ?/sec    1.00      8.7±0.10ms        ? ?/sec
first_value update_bench nulls=90%, filter=false                              2.00     34.1±0.11ms        ? ?/sec    1.00     17.0±0.09ms        ? ?/sec
first_value update_bench nulls=90%, filter=true                               4.74     46.8±0.27ms        ? ?/sec    1.00      9.9±0.07ms        ? ?/sec
first_value update_bench struct(i64,utf8,f64) nulls=0%                        1.94     36.2±0.31ms        ? ?/sec    1.00     18.6±0.20ms        ? ?/sec
first_value update_bench struct(i64,utf8,f64) nulls=90%                       1.86     39.4±0.43ms        ? ?/sec    1.00     21.2±0.25ms        ? ?/sec
last_value evaluate_bench nulls=0%, filter=false, all                         3.42     21.0±2.26µs        ? ?/sec    1.00     6.1±51.51µs        ? ?/sec
last_value evaluate_bench nulls=0%, filter=false, first(2)                    3.22     22.6±2.33µs        ? ?/sec    1.00     7.0±45.41µs        ? ?/sec
last_value evaluate_bench nulls=0%, filter=true, all                          5.07     21.3±3.25µs        ? ?/sec    1.00     4.2±32.06µs        ? ?/sec
last_value evaluate_bench nulls=0%, filter=true, first(2)                     4.20     23.3±2.80µs        ? ?/sec    1.00     5.6±31.60µs        ? ?/sec
last_value evaluate_bench nulls=90%, filter=false, all                        19.23    20.1±2.64µs        ? ?/sec    1.00  1045.3±101.32ns        ? ?/sec
last_value evaluate_bench nulls=90%, filter=false, first(2)                   8.90     23.0±2.62µs        ? ?/sec    1.00      2.6±0.22µs        ? ?/sec
last_value evaluate_bench nulls=90%, filter=true, all                         2.39     20.8±2.43µs        ? ?/sec    1.00     8.7±77.71µs        ? ?/sec
last_value evaluate_bench nulls=90%, filter=true, first(2)                    2.07     22.0±2.48µs        ? ?/sec    1.00    10.6±82.73µs        ? ?/sec
last_value merge_bench list<i64>[4] nulls=0%                                  2.38     39.1±0.25ms        ? ?/sec    1.00     16.5±0.25ms        ? ?/sec
last_value merge_bench list<i64>[4] nulls=90%                                 2.35     39.5±0.23ms        ? ?/sec    1.00     16.8±0.19ms        ? ?/sec
last_value merge_bench list<struct(i64,utf8)>[4] nulls=0%                     2.17     42.3±0.39ms        ? ?/sec    1.00     19.5±0.22ms        ? ?/sec
last_value merge_bench list<struct(i64,utf8)>[4] nulls=90%                    2.10     42.8±0.34ms        ? ?/sec    1.00     20.4±0.32ms        ? ?/sec
last_value merge_bench map<utf8,i64> nulls=0%                                 2.19     41.9±0.29ms        ? ?/sec    1.00     19.2±0.21ms        ? ?/sec
last_value merge_bench map<utf8,i64> nulls=90%                                2.13     43.8±0.38ms        ? ?/sec    1.00     20.5±0.30ms        ? ?/sec
last_value merge_bench nulls=0%, filter=false                                 2.64     36.4±0.24ms        ? ?/sec    1.00     13.8±0.21ms        ? ?/sec
last_value merge_bench nulls=0%, filter=true                                  2.62     36.5±0.18ms        ? ?/sec    1.00     13.9±0.29ms        ? ?/sec
last_value merge_bench nulls=90%, filter=false                                2.61     47.0±0.42ms        ? ?/sec    1.00     18.0±0.11ms        ? ?/sec
last_value merge_bench nulls=90%, filter=true                                 2.66     47.4±0.24ms        ? ?/sec    1.00     17.8±0.08ms        ? ?/sec
last_value merge_bench struct(i64,utf8,f64) nulls=0%                          2.21     41.4±0.28ms        ? ?/sec    1.00     18.8±0.19ms        ? ?/sec
last_value merge_bench struct(i64,utf8,f64) nulls=90%                         2.08     44.8±0.32ms        ? ?/sec    1.00     21.5±0.29ms        ? ?/sec
last_value trivial_update_bench nulls=0%, ignore_nulls=false                  1.03      2.8±0.06µs        ? ?/sec    1.00      2.7±0.07µs        ? ?/sec
last_value trivial_update_bench nulls=0%, ignore_nulls=true                   1.03      2.8±0.16µs        ? ?/sec    1.00      2.7±0.06µs        ? ?/sec
last_value trivial_update_bench nulls=90%, ignore_nulls=false                 1.00      3.0±0.05µs        ? ?/sec    1.00      3.0±0.05µs        ? ?/sec
last_value trivial_update_bench nulls=90%, ignore_nulls=true                  1.01      7.5±0.03µs        ? ?/sec    1.00      7.4±0.07µs        ? ?/sec
last_value update_bench list<i64>[4] nulls=0%                                 2.04     33.2±0.23ms        ? ?/sec    1.00     16.3±0.29ms        ? ?/sec
last_value update_bench list<i64>[4] nulls=90%                                2.01     33.7±0.34ms        ? ?/sec    1.00     16.8±0.19ms        ? ?/sec
last_value update_bench list<struct(i64,utf8)>[4] nulls=0%                    1.89     37.0±0.37ms        ? ?/sec    1.00     19.6±0.29ms        ? ?/sec
last_value update_bench list<struct(i64,utf8)>[4] nulls=90%                   1.86     37.7±0.26ms        ? ?/sec    1.00     20.3±0.27ms        ? ?/sec
last_value update_bench map<utf8,i64> nulls=0%                                1.91     36.4±0.26ms        ? ?/sec    1.00     19.1±0.15ms        ? ?/sec
last_value update_bench map<utf8,i64> nulls=90%                               1.87     38.5±0.37ms        ? ?/sec    1.00     20.6±0.28ms        ? ?/sec
last_value update_bench nulls=0%, filter=false                                2.22     31.5±0.26ms        ? ?/sec    1.00     14.2±0.09ms        ? ?/sec
last_value update_bench nulls=0%, filter=true                                 5.64     47.8±0.22ms        ? ?/sec    1.00      8.5±0.09ms        ? ?/sec
last_value update_bench nulls=90%, filter=false                               2.35     42.6±0.28ms        ? ?/sec    1.00     18.1±0.13ms        ? ?/sec
last_value update_bench nulls=90%, filter=true                                5.83     54.4±0.33ms        ? ?/sec    1.00      9.3±0.05ms        ? ?/sec
last_value update_bench struct(i64,utf8,f64) nulls=0%                         1.93     36.0±0.20ms        ? ?/sec    1.00     18.6±0.19ms        ? ?/sec
last_value update_bench struct(i64,utf8,f64) nulls=90%                        1.83     39.0±0.55ms        ? ?/sec    1.00     21.3±0.26ms        ? ?/sec

Resource Usage

first_last — base (merge-base)

Metric Value
Wall time 1245.3s
Peak memory 2.8 GiB
Avg memory 128.7 MiB
CPU user 1210.6s
CPU sys 10.3s
Peak spill 0 B

first_last — branch

Metric Value
Wall time 1115.3s
Peak memory 2.8 GiB
Avg memory 119.4 MiB
CPU user 1053.3s
CPU sys 18.9s
Peak spill 0 B

File an issue against this benchmark runner

@gabotechs gabotechs 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.

+1 to @jayzhan211's comment about increasing test coverage in clustered_partial_stream.rs, but otherwise this looks good!

FIRST_VALUE(v ORDER BY v) now runs the single primitive key accumulator,
so ORDER BY v, k alone left its shrink_to_fit untested. Run both
orderings, so each case fails if its own accumulator stops releasing
its scratch.

Suggested by @jayzhan211.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

functions Changes to functions implementation physical-plan Changes to the physical-plan crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve performance of grouped first_value / last_value ordered by a single primitive column

4 participants