Skip to content

ext/standard: Speed up strict in_array()/array_search() with a string needle - #25280

Merged
ndossche merged 1 commit into
php:masterfrom
adapik:fast-in-array-string
Nov 9, 2026
Merged

ndossche merged 1 commit into
php:masterfrom
adapik:fast-in-array-string

Conversation

@adapik

@adapik adapik commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

In 107ae86 specialized comparisons were added to in_array() for integer and string needles, to avoid repeating the type checks on every iteration. That covered loose mode only. Strict mode kept going through the generic identity check for every element.

Later @nikic noticed the gap for integers: it doesn't make sense that strict=false is much faster than strict just for lack of a specialized codepath, and added an integer loop for strict mode (ea86a92). Strings were not touched, so strict mode with a string needle still calls zend_is_identical() for every element.

The result is that the same gap is still there for strings. On master in_array($str, $list, true) is about 2x slower than the loose version when the needle isn't found (1.7x in instructions, 2.0x in time).

This PR adds the missing loop, next to the integer one. After it, strict and loose are on par for string needles (about 21 vs 23 instructions per element).

Instructions per call, array of N strings (callgrind, release build):

  • N=5: -15% if found, -27% if not found
  • N=50: -23% / -45%
  • N=500: -23% / -48%
  • N=5000: -23% / -49%

At N=500 the time goes from 95 to 62 ms (found) and from 79 to 40 ms (not found). Other needle types are unchanged, except null, which costs 3 more instructions per call.

@ndossche

ndossche commented Nov 7, 2026

Copy link
Copy Markdown
Member

Callgrind measures the instructions as if it operates as an in-order CPU, and treats all instructions as equally expensive.
Please share your benchmark script and test the benchmark using hyperfine.
Code itself looks good but we need better evidence this is worth it

@adapik

adapik commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor Author

@ndossche Here are wall-clock numbers with hyperfine instead.

The main motivation: a strict check should be at least as fast as a loose one, and right now it's slower.

Benchmark script

Release build (./configure --disable-all --enable-cli), run with -n, 30 runs each, mean times in ms. Time includes process startup. The needle is never found and is as long as the last element, so each comparison has to look at the string contents.

bench.php:

<?php
[$_, $mode, $size] = $argv;
$size = (int)$size;

$list = [];
for ($i = 0; $i < $size; $i++) {
    $list[] = "item_{$i}_xxxxxxxx";
}
$needle = 'item_' . ($size - 1) . '_xxxxxxxY';

$calls = intdiv(20_000_000, $size);
$hits = 0;
if ($mode === 'strict') {
    for ($i = 0; $i < $calls; $i++) {
        if (in_array($needle, $list, true)) $hits++;
    }
} else {
    for ($i = 0; $i < $calls; $i++) {
        if (in_array($needle, $list)) $hits++;
    }
}
echo $hits, "\n";
  1. master only, loose vs strict:
hyperfine -N --warmup 3 --runs 30 -L mode strict,loose -L size 10,100,1000 \
  "./php_master -n bench.php {mode} {size}"
array size loose (ms) strict (ms) slowdown
10 55.7 64.7 1.16x
100 42.8 52.2 1.22x
1000 42.0 51.1 1.22x
  1. strict only, master vs patched:
hyperfine -N --warmup 3 --runs 30 -L bin master,patched -L size 10,100,1000 \
  "./php_{bin} -n bench.php strict {size}"
array size master (ms) patched (ms) speedup
10 65.2 48.0 1.36x
100 51.8 35.9 1.44x
1000 51.2 36.5 1.40x

Note: I updated the branch but only to resolve the conflict in NEWS, all the rest is still the same.

@adapik
adapik force-pushed the fast-in-array-string branch from 65fb94a to 9f15a48 Compare November 7, 2026 17:49

@ndossche ndossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, please add an entry to UPGRADING under "performance improvements"

@adapik
adapik force-pushed the fast-in-array-string branch from 9f15a48 to 2f5f818 Compare November 8, 2026 17:19
@adapik

adapik commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

UPGRADING entry added, thanks for the review!

@ndossche
ndossche merged commit 43f610c into php:master Nov 9, 2026
18 checks passed
@ndossche

ndossche commented Nov 9, 2026

Copy link
Copy Markdown
Member

Merged,thanks!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants