Skip to content

perf: Optimize strpos for scalar needle, plus optimize UTF-8 codepath#20754

Open
neilconway wants to merge 5 commits intoapache:mainfrom
neilconway:neilc/optimize-strpos-finder
Open

perf: Optimize strpos for scalar needle, plus optimize UTF-8 codepath#20754
neilconway wants to merge 5 commits intoapache:mainfrom
neilconway:neilc/optimize-strpos-finder

Conversation

@neilconway
Copy link
Contributor

@neilconway neilconway commented Mar 6, 2026

Which issue does this PR close?

Rationale for this change

This PR implements two mostly unrelated optimizations for strpos:

  1. When the needle is scalar, we can build a single memmem::Finder and use it to search each row of the haystack. It turns out that this is significantly faster than using memchr, and the cost of constructing the finder is cheap because it is amortized over the batch.
  2. We previously optimized strpos to use memchr for searching when both haystack and needle are ASCII-only (perf: Optimize strpos() for ASCII-only inputs #20295). That was needlessly conservative: UTF-8 is self-stabilizing, so it should be safe to use memchr to search for matches for any combination of ASCII and UTF-8 needle and haystack.

The performance improvement depends on a bunch of factors (ASCII vs. UTF-8, scalar vs array needle, length of haystack strings), but ranges from 5% for short ASCII strings with a scalar needle to 15x for long UTF-8 strings with a scalar needle.

What changes are included in this PR?

  • Improve SLT test coverage for strpos
  • Refactor and extend strpos benchmarks to cover the scalar case
  • Implement optimizations described above
  • Code cleanup and refactoring for the strpos implementation

Are these changes tested?

Yes; new test cases and benchmarks added.

Are there any user-facing changes?

No.

@github-actions github-actions bot added sqllogictest SQL Logic Tests (.slt) functions Changes to functions implementation labels Mar 6, 2026
@neilconway
Copy link
Contributor Author

Benchmarks:

⏺ ┌───────────────────────────────────────┬────────────────┬───────────────┬─────────┐
  │               Benchmark               │      Base      │    Target     │ Speedup │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ StringArray, array needle, ASCII      │                │               │         │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_8                             │ 268.6±4.09µs   │ 254.4±3.85µs  │ 1.06x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_32                            │ 314.8±6.02µs   │ 292.9±3.65µs  │ 1.07x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_128                           │ 598.6±4.35µs   │ 583.1±4.03µs  │ 1.03x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_4096                          │ 11.5±0.08ms    │ 12.1±1.45ms   │ 0.96x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ StringArray, array needle, UTF-8      │                │               │         │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_8                             │ 437.4±3.30µs   │ 370.9±2.73µs  │ 1.18x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_32                            │ 1112.4±3.72µs  │ 666.5±2.64µs  │ 1.67x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_128                           │ 3.7±0.03ms     │ 1668.6±3.13µs │ 2.20x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_4096                          │ 111.5±0.83ms   │ 39.8±0.22ms   │ 2.80x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ StringArray, scalar needle, ASCII     │                │               │         │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_8                             │ 216.1±4.17µs   │ 214.1±2.68µs  │ 1.01x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_32                            │ 243.3±2.79µs   │ 206.1±2.20µs  │ 1.18x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_128                           │ 548.1±7.15µs   │ 344.8±3.69µs  │ 1.59x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_4096                          │ 11.2±0.09ms    │ 6.9±0.19ms    │ 1.62x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ StringArray, scalar needle, UTF-8     │                │               │         │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_8                             │ 399.3±2.78µs   │ 277.8±3.43µs  │ 1.44x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_32                            │ 1154.6±5.03µs  │ 365.5±4.52µs  │ 3.16x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_128                           │ 4.3±0.08ms     │ 556.0±5.66µs  │ 7.66x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_4096                          │ 128.7±0.40ms   │ 8.7±0.12ms    │ 14.81x  │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ StringViewArray, array needle, ASCII  │                │               │         │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_8                             │ 368.5±2.41µs   │ 351.0±1.93µs  │ 1.05x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_32                            │ 432.7±5.04µs   │ 430.5±3.72µs  │ 1.01x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_128                           │ 763.2±3.76µs   │ 755.5±2.21µs  │ 1.01x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_4096                          │ 12.0±0.14ms    │ 12.0±0.11ms   │ 1.00x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ StringViewArray, array needle, UTF-8  │                │               │         │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_8                             │ 461.0±6.27µs   │ 397.0±2.40µs  │ 1.16x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_32                            │ 1132.5±10.63µs │ 699.0±4.09µs  │ 1.62x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_128                           │ 3.7±0.03ms     │ 1654.7±5.26µs │ 2.22x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_4096                          │ 112.2±0.22ms   │ 40.1±0.05ms   │ 2.80x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ StringViewArray, scalar needle, ASCII │                │               │         │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_8                             │ 234.7±2.46µs   │ 221.4±2.93µs  │ 1.06x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_32                            │ 271.4±3.63µs   │ 234.5±3.36µs  │ 1.16x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_128                           │ 596.0±3.50µs   │ 394.1±2.32µs  │ 1.51x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_4096                          │ 11.7±0.27ms    │ 7.5±0.19ms    │ 1.57x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ StringViewArray, scalar needle, UTF-8 │                │               │         │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_8                             │ 421.6±2.40µs   │ 299.2±4.16µs  │ 1.41x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_32                            │ 1167.0±14.28µs │ 386.8±3.41µs  │ 3.02x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_128                           │ 4.1±0.01ms     │ 574.6±4.25µs  │ 7.18x   │
  ├───────────────────────────────────────┼────────────────┼───────────────┼─────────┤
  │ str_len_4096                          │ 125.7±0.34ms   │ 8.7±0.10ms    │ 14.41x  │
  └───────────────────────────────────────┴────────────────┴───────────────┴─────────┘

@neilconway
Copy link
Contributor Author

FYI @Jefffrey -- we discussed this in the previous strpos PR (#20295). Turns out that memmem::Finder can be a significant win 😊

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

Labels

functions Changes to functions implementation sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize strpos: (1) scalar needle, and (2) Unicode input

1 participant