Skip to content

perf: Cast entire Date32 array to Date64 on 1st failure#21948

Open
huymq1710 wants to merge 9 commits intoapache:mainfrom
huymq1710:cast-entire-date32-array
Open

perf: Cast entire Date32 array to Date64 on 1st failure#21948
huymq1710 wants to merge 9 commits intoapache:mainfrom
huymq1710:cast-entire-date32-array

Conversation

@huymq1710
Copy link
Copy Markdown
Contributor

@huymq1710 huymq1710 commented Apr 30, 2026

Which issue does this PR close?

Rationale for this change

// When `Date32` formatting fails (due to time specifiers),
// the code called cast individually for each row instead of converting the entire array
let date64_value = cast(&data_array.slice(idx, 1), &Date64)?;

What changes are included in this PR?

Cast the entire Date32 array to Date64 once on first failure, instead of per-row

cast(data_array.as_ref(), &Date64

Are these changes tested?

Yes

  • Add 2 benchmarks:
    • Date32 + datetime patterns (all rows trigger the fallback)
    • Date32 + mixed patterns (roughly half do)
  ┌─────────────────────────────────────┬───────────────────┬───────────┐
  │              Benchmark              │      Format       │  Change   │
  ├─────────────────────────────────────┼───────────────────┼───────────┤
  │ array_date_only_patterns_1000       │ array, date-only  │ No change │
  ├─────────────────────────────────────┼───────────────────┼───────────┤
  │ array_datetime_patterns_1000        │ array, datetime   │ -3.21%    │
  ├─────────────────────────────────────┼───────────────────┼───────────┤
  │ array_mixed_patterns_1000           │ array, mixed      │ +1.41%    │
  ├─────────────────────────────────────┼───────────────────┼───────────┤
  │ scalar_date_only_pattern_1000       │ scalar, date-only │ No change │
  ├─────────────────────────────────────┼───────────────────┼───────────┤
  │ scalar_datetime_pattern_1000        │ scalar, datetime  │ No change │
  ├─────────────────────────────────────┼───────────────────┼───────────┤
  │ array_date32_datetime_patterns_1000 │ array, datetime   │ -20.24%   │
  ├─────────────────────────────────────┼───────────────────┼───────────┤
  │ array_date32_mixed_patterns_1000    │ array, mixed      │ -15.74%   │
  └─────────────────────────────────────┴───────────────────┴───────────┘
Detail
cargo bench --bench to_char          
   Compiling datafusion-functions v53.1.0 (/Users/qmac/Documents/GitHub/datafusion/datafusion/functions)
    Finished `bench` profile [optimized] target(s) in 1m 10s
     Running benches/to_char.rs (target/release/deps/to_char-d2acea4b7a3e2fba)
Gnuplot not found, using plotters backend
Benchmarking to_char_array_date_only_patterns_1000: Warming up for 3.00Benchmarking to_char_array_date_only_patterns_1000: Collecting 100 sampto_char_array_date_only_patterns_1000
                        time:   [104.01 µs 104.37 µs 104.74 µs]
                        change: [−0.5692% +0.3244% +1.0788%] (p = 0.47 > 0.05)
                        No change in performance detected.
Found 3 outliers among 100 measurements (3.00%)
  1 (1.00%) low mild
  2 (2.00%) high mild

Benchmarking to_char_array_datetime_patterns_1000: Warming up for 3.000Benchmarking to_char_array_datetime_patterns_1000: Collecting 100 samplto_char_array_datetime_patterns_1000
                        time:   [165.96 µs 166.43 µs 166.94 µs]
                        change: [−4.1660% −3.2055% −2.3626%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  2 (2.00%) low mild
  3 (3.00%) high mild
  1 (1.00%) high severe

Benchmarking to_char_array_mixed_patterns_1000: Collecting 100 samples to_char_array_mixed_patterns_1000
                        time:   [139.58 µs 140.02 µs 140.49 µs]
                        change: [+1.0116% +1.4122% +1.8178%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 3 outliers among 100 measurements (3.00%)
  1 (1.00%) low mild
  2 (2.00%) high mild

Benchmarking to_char_scalar_date_only_pattern_1000: Warming up for 3.00Benchmarking to_char_scalar_date_only_pattern_1000: Collecting 100 sampto_char_scalar_date_only_pattern_1000
                        time:   [60.073 µs 63.184 µs 66.309 µs]
                        change: [−4.0228% +0.6839% +6.1581%] (p = 0.79 > 0.05)
                        No change in performance detected.

Benchmarking to_char_scalar_datetime_pattern_1000: Warming up for 3.000Benchmarking to_char_scalar_datetime_pattern_1000: Collecting 100 samplto_char_scalar_datetime_pattern_1000
                        time:   [117.65 µs 124.76 µs 131.84 µs]
                        change: [−3.3009% +2.6889% +8.6148%] (p = 0.37 > 0.05)
                        No change in performance detected.
Found 30 outliers among 100 measurements (30.00%)
  16 (16.00%) low mild
  14 (14.00%) high mild

Benchmarking to_char_array_date32_datetime_patterns_1000: Warming up foBenchmarking to_char_array_date32_datetime_patterns_1000: Collecting 10to_char_array_date32_datetime_patterns_1000
                        time:   [289.67 µs 290.77 µs 291.88 µs]
                        change: [−20.661% −20.240% −19.863%] (p = 0.00 < 0.05)
                        Performance has improved.

Benchmarking to_char_array_date32_mixed_patterns_1000: Warming up for 3Benchmarking to_char_array_date32_mixed_patterns_1000: Collecting 100 sto_char_array_date32_mixed_patterns_1000
                        time:   [194.47 µs 195.28 µs 196.11 µs]
                        change: [−16.230% −15.738% −15.285%] (p = 0.00 < 0.05)
                        Performance has improved.

Are there any user-facing changes?

No

@github-actions github-actions Bot added the functions Changes to functions implementation label Apr 30, 2026
@huymq1710 huymq1710 changed the title Cast entire date32 array perf: Cast entire Date32 array to Date64 on 1st failure Apr 30, 2026
Comment thread datafusion/functions/benches/to_char.rs Outdated
Comment thread datafusion/functions/src/datetime/to_char.rs
Comment thread datafusion/functions/src/datetime/to_char.rs
@huymq1710
Copy link
Copy Markdown
Contributor Author

Thank you. I updated

@kumarUjjawal kumarUjjawal requested a review from alamb May 2, 2026 04:40
@Omega359
Copy link
Copy Markdown
Contributor

Omega359 commented May 3, 2026

Previous work on this might show some insight - #15361 (comment)

Copy link
Copy Markdown
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

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

Thanks @huymq1710 and @kumarUjjawal and @Omega359

THis looks good to me at a high level -- I would like to get the benchmark in place so we can verify performance

// handle time specifiers and falls back to a Date64 cast.

// Covers full fallback (every row triggers the cast)
c.bench_function("to_char_array_date32_datetime_patterns_1000", |b| {
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.

Thank you @huymq1710

Can you please put this new benchmark into a separate PR so that we can use our automated benchmark runner to compare the performance of main and your proposal?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Got it, I temporary removed benchmark changes, I will create other PR to add back after this PR is merged

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.

@alamb One clarification, do you want this merged first or the pr with benchmark?

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.

Typically I like to merge the PR with the benchmark first, then merge up the PR with performance improvements, and then validate the benchmark results using the run benchmarks command.

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.

Okay, thanks!

@kumarUjjawal
Copy link
Copy Markdown
Contributor

@huymq1710 can you open the pr with the benchmark

@huymq1710
Copy link
Copy Markdown
Contributor Author

I see, created

geoffreyclaude pushed a commit to geoffreyclaude/datafusion that referenced this pull request May 4, 2026
## Which issue does this PR close?

<!--
We generally require a GitHub issue to be filed for all bug fixes and
enhancements and this helps us generate change logs for our releases.
You can link an issue to this PR using the GitHub syntax. For example
`Closes apache#123` indicates that this PR will close issue apache#123.
-->

- Closes #.

## Rationale for this change

<!--
Why are you proposing this change? If this is already explained clearly
in the issue then this section is not needed.
Explaining clearly why changes are proposed helps reviewers understand
your changes and offer better suggestions for fixes.
-->

## What changes are included in this PR?

<!--
There is no need to duplicate the description in the issue here but it
is sometimes worth providing a summary of the individual changes in this
PR.
-->

Benchmark for apache#21948

## Are these changes tested?

<!--
We typically require tests for all PRs in order to:
1. Prevent the code from being accidentally broken by subsequent changes
2. Serve as another way to document the expected behavior of the code

If tests are not included in your PR, please explain why (for example,
are they covered by existing tests)?
-->

## Are there any user-facing changes?

<!--
If there are user-facing changes then we may require documentation to be
updated before approving the PR.
-->

<!--
If there are any breaking changes to public APIs, please add the `api
change` label.
-->
@kumarUjjawal
Copy link
Copy Markdown
Contributor

run benchmark

@adriangbot
Copy link
Copy Markdown

Hi @kumarUjjawal, run benchmark requires benchmark names (#21948 (comment)).

Supported benchmarks:

  • Standard: clickbench_1, clickbench_extended, clickbench_partitioned, clickbench_pushdown, external_aggr, smj, sort_pushdown, sort_pushdown_inexact, sort_pushdown_inexact_overlap, sort_pushdown_inexact_unsorted, sort_pushdown_sorted, topk_tpch, tpcds, tpch, tpch10, tpch_mem, tpch_mem10
  • Criterion: (any)

Usage:

run benchmark <name>           # run specific benchmark(s)
run benchmarks                 # run default suite
run benchmarks <name1> <name2> # run specific benchmarks

Per-side configuration (run benchmark tpch followed by):

env:
SHARED_SETTING: enabled
baseline:
ref: v45.0.0
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 1G
changed:
ref: v46.0.0
env:
DATAFUSION_RUNTIME_MEMORY_LIMIT: 2G

File an issue against this benchmark runner

@kumarUjjawal
Copy link
Copy Markdown
Contributor

run benchmark to_char

@adriangbot
Copy link
Copy Markdown

🤖 Criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4368798241-2000-xvvnf 6.12.68+ #1 SMP Wed Apr 1 02:23:28 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 cast-entire-date32-array (205f116) to b2fd2d3 (merge-base) diff
BENCH_NAME=to_char
BENCH_COMMAND=cargo bench --features=parquet --bench to_char
BENCH_FILTER=
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot
Copy link
Copy Markdown

🤖 Criterion benchmark completed (GKE) | trigger

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

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                                          cast-entire-date32-array               main
-----                                          ------------------------               ----
to_char_array_date32_datetime_patterns_1000    1.00    621.4±4.11µs        ? ?/sec    1.32    821.3±3.57µs        ? ?/sec
to_char_array_date32_mixed_patterns_1000       1.00    402.7±9.22µs        ? ?/sec    1.25    504.8±8.98µs        ? ?/sec
to_char_array_date_only_patterns_1000          1.01    175.3±1.79µs        ? ?/sec    1.00    173.9±2.11µs        ? ?/sec
to_char_array_datetime_patterns_1000           1.00    372.1±4.27µs        ? ?/sec    1.01    376.7±3.66µs        ? ?/sec
to_char_array_mixed_patterns_1000              1.00    282.6±4.08µs        ? ?/sec    1.01    285.5±3.33µs        ? ?/sec
to_char_scalar_date_only_pattern_1000          1.04   149.9±21.47µs        ? ?/sec    1.00   144.8±21.11µs        ? ?/sec
to_char_scalar_datetime_pattern_1000           1.00   293.6±59.07µs        ? ?/sec    1.03   302.4±47.62µs        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 80.0s
Peak memory 4.3 GiB
Avg memory 4.2 GiB
CPU user 92.7s
CPU sys 0.8s
Peak spill 0 B

branch

Metric Value
Wall time 75.0s
Peak memory 4.2 GiB
Avg memory 4.2 GiB
CPU user 91.7s
CPU sys 0.3s
Peak spill 0 B

File an issue against this benchmark runner

@kumarUjjawal
Copy link
Copy Markdown
Contributor

Benchmark looks good!

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

Labels

functions Changes to functions implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize to_char for array conversions

5 participants