Skip to content

Improve performance of in_array - #21105

Open
jorgsowa wants to merge 2 commits into
php:masterfrom
jorgsowa:perf/in_array
Open

jorgsowa wants to merge 2 commits into
php:masterfrom
jorgsowa:perf/in_array

Conversation

@jorgsowa

@jorgsowa jorgsowa commented Jan 31, 2026 •

Copy link
Copy Markdown
Contributor

Added checks for packed array inside function _php_search_array that is used by in_array() and array_search().

Function in_array() is used frequently with lists, and this modification improves the performance.

For array_search() in all cases the diference is negligible.

+-----------+------------------+--------+--------------------------------------+
| arraySize | randomKeysSize   | strict | Result                               |
+-----------+------------------+--------+--------------------------------------+
| 5000      | 5000             | false  | master 1.00 ± 0.00× faster than new  |
| 5000      | 5000             | true   | master 1.00 ± 0.00× faster than new  |
| 5         | 5                | true   | master 1.00 ± 0.09× faster than new  |
| 5         | 5                | false  | master 1.00 ± 0.09× faster than new  |
+-----------+------------------+--------+--------------------------------------+

For in_array() bigger the array, faster the execution.

| arraySize | randomKeysSize | packed | valueType | strict | Result (new vs master)    
|-----------|----------------|--------|-----------|--------|----------------------------------
| 500,000   | 100            | yes    | int       | false  | 1.32 ± 0.01 × faster     
| 500,000   | 100            | yes    | int       | true   | 1.19 ± 0.02 × faster        
| 50,000    | 100            | yes    | mixed     | false  | 1.07 ± 0.16 × faster         
| 500,000   | 100            | yes    | mixed     | true   | 1.19 ± 0.01 × faster       
| 500,000   | 100            | yes    | string    | false  | 1.11 ± 0.01 × faster     
| 500,000   | 100            | yes    | string    | true   | 1.08 ± 0.01 × faster   
| 50,000    | 100            | no     | string    | false  | 1.01 ± 0.01 × faster            
| 50,000    | 100            | no     | string    | true   | 1.00 ± 0.01 × faster       
| 500       | 100            | yes    | int       | false  | 1.05 ± 0.12 x faster          
| 500       | 100            | yes    | int       | true   | 1.05 ± 0.06 x faster   
| 500       | 100            | yes    | mixed     | false  | 1.03 ± 0.01 x faster   
| 500       | 100            | yes    | mixed     | true   | 1.08 ± 0.30 x faster            
| 500       | 100            | yes    | string    | false  | 1.08 ± 0.15 x faster        
| 500       | 100            | yes    | string    | true   | 1.04 ± 0.03 x faster      
| 500       | 100            | no     | string    | false  | 1.01 ± 0.18 x faster           
| 500       | 100            | no     | string    | true   | 1.01 ± 0.14 x slower       
| 5         | 5              | yes    | mixed     | true   | 1.01 ± 0.23 x faster (10000 loops)
| 5         | 5              | no     | mixed     | true   | 1.01 ± 0.05 x faster (10000 loops)

Benchmark code
<?php

$arraySize = 100;
$randomKeysSize = 100;
$isPacked = true;
$valueType = 0; // 0 - only strings, 1 - only integers, 2 - string/int 50/50
$strict = false;
$in_array = true;

$array = [];
for ($i = 0; $i < $arraySize; $i++) {
    if($isPacked) {
        $array[] = ($valueType === 0 || $i % $valueType == 1) ? "String $i" : $i;
    } else {
        $array["key_$i"] = "String $i";
    }
}

mt_srand(1234);
$randomKeys = array_rand($array, $randomKeysSize);
$values = array_map(fn($key) => $array[$key], $randomKeys);

for ($i = 0; $i < 100; $i++) {
    foreach ($values as $value) {
        if($in_array) {
            in_array($value, $array, $strict);
        } else {
            array_search($value, $array, $strict);
        }
    }
}

Command I used for comparison:
hyperfine --warmup 3 "sapi/cli/php_master test_in_array.php" "sapi/cli/php test_in_array.php"

Must be merged after merging the extended tests:

Comment thread ext/standard/array.c Outdated
Comment thread ext/standard/array.c Outdated
@github-actions

Copy link
Copy Markdown

AWS x86_64 (c6id.metal)

Attribute Value
Environment aws
Instance type c6id.metal
Architecture x86_64
CPU Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, 64 cores @ 2900 MHz
CPU settings disabled deeper C-states, disabled turbo boost, disabled hyper-threading
RAM 251 GB
Kernel 6.12.66-88.122.amzn2023.x86_64
OS Amazon Linux 2023.10.20260202
GCC 14.2.1
Time 2026-02-24 18:49:07 UTC
Job details https://github.com/php/php-src/actions/runs/22365239226 (Artifacts)
Changeset https://github.com/php/php-src/compare/7158e6376e..c9c025d489

Laravel 12.11.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7158e63 0.38896 0.39196 0.00052 0.13% 0.38951 0.00% 0.38939 0.00% 3.203 0.000 1.000 25.26 MB
PHP - perf/in_array 0.38876 0.39009 0.00035 0.09% 0.38923 -0.07% 0.38914 -0.06% 0.841 3.657 0.000 25.25 MB

Symfony 2.8.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7158e63 0.65851 0.66081 0.00046 0.07% 0.65928 0.00% 0.65921 0.00% 1.574 0.000 1.000 25.30 MB
PHP - perf/in_array 0.66310 0.66701 0.00074 0.11% 0.66384 0.69% 0.66365 0.67% 2.524 -8.614 0.000 25.30 MB

Wordpress 6.9 main page - 50 iterations, 20 warmups, 20 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7158e63 0.58594 0.58994 0.00069 0.12% 0.58670 0.00% 0.58652 0.00% 3.112 0.000 1.000 25.39 MB
PHP - perf/in_array 0.58365 0.59109 0.00125 0.21% 0.58444 -0.39% 0.58406 -0.42% 3.764 7.263 0.000 25.34 MB

bench.php - 50 iterations, 20 warmups, 2 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7158e63 0.44446 0.44655 0.00050 0.11% 0.44551 0.00% 0.44548 0.00% 0.050 0.000 1.000 25.39 MB
PHP - perf/in_array 0.44322 0.44613 0.00068 0.15% 0.44494 -0.13% 0.44487 -0.14% -0.284 4.147 0.000 25.34 MB

@github-actions

Copy link
Copy Markdown

AWS x86_64 (c6id.metal)

Attribute Value
Environment aws
Instance type c6id.metal
Architecture x86_64
CPU Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, 64 cores @ 2900 MHz
CPU settings disabled deeper C-states, disabled turbo boost, disabled hyper-threading
RAM 251 GB
Kernel 6.12.66-88.122.amzn2023.x86_64
OS Amazon Linux 2023.10.20260202
GCC 14.2.1
Time 2026-02-24 22:34:32 UTC
Job details https://github.com/php/php-src/actions/runs/22372998923 (Artifacts)
Changeset https://github.com/php/php-src/compare/7158e6376e..c9c025d489

Laravel 12.11.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7158e63 0.38922 0.39076 0.00034 0.09% 0.38964 0.00% 0.38954 0.00% 1.683 0.000 1.000 25.27 MB
PHP - perf/in_array 0.38890 0.39144 0.00051 0.13% 0.38962 -0.00% 0.38958 0.01% 1.263 0.596 0.551 25.26 MB

Symfony 2.8.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7158e63 0.65959 0.66443 0.00074 0.11% 0.66027 0.00% 0.66011 0.00% 3.932 0.000 1.000 25.32 MB
PHP - perf/in_array 0.66380 0.66812 0.00075 0.11% 0.66462 0.66% 0.66441 0.65% 2.880 -8.428 0.000 25.32 MB

Wordpress 6.9 main page - 50 iterations, 20 warmups, 20 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7158e63 0.58585 0.58895 0.00066 0.11% 0.58650 0.00% 0.58635 0.00% 2.776 0.000 1.000 25.39 MB
PHP - perf/in_array 0.58356 0.58692 0.00080 0.14% 0.58419 -0.39% 0.58393 -0.41% 2.552 7.483 0.000 25.33 MB

bench.php - 50 iterations, 20 warmups, 2 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7158e63 0.44437 0.44702 0.00054 0.12% 0.44570 0.00% 0.44571 0.00% 0.095 0.000 1.000 25.39 MB
PHP - perf/in_array 0.44305 0.44656 0.00085 0.19% 0.44496 -0.17% 0.44505 -0.15% -0.342 4.581 0.000 25.33 MB

@iluuu1994

iluuu1994 commented Feb 24, 2026 •

Copy link
Copy Markdown
Member

Hmm, the two benchmark runs do seem to indicate a significant slowdown in Symfony. Could be overspecialization. Hard to say whether the trade-off is worth it. I'll try benchmarking locally tomorrow to verify.

@Girgias

Girgias commented Feb 26, 2026

Copy link
Copy Markdown
Member

I think at this point it just makes more sense to split _php_search_array into two C functions, one for in_array and the other for array_search.

@iluuu1994

Copy link
Copy Markdown
Member

This is inlined (or should be at least, should be zend_always_inline), so not sure if there's a point to splitting, unless the duplication can be reduced.

@github-actions

github-actions Bot commented Mar 4, 2026

Copy link
Copy Markdown

AWS x86_64 (c6id.metal)

Attribute Value
Environment aws
Instance type c6id.metal
Architecture x86_64
CPU Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, 64 cores @ 2900 MHz
CPU settings disabled deeper C-states, disabled turbo boost, disabled hyper-threading
RAM 251 GB
Kernel 6.12.66-88.122.amzn2023.x86_64
OS Amazon Linux 2023.10.20260202
GCC 14.2.1
Time 2026-03-04 09:49:20 UTC
Job details https://github.com/php/php-src/actions/runs/22663940877 (Artifacts)
Changeset https://github.com/php/php-src/compare/7a1c2612c0..b107170a01

Laravel 12.11.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7a1c261 0.38828 0.38962 0.00020 0.05% 0.38873 0.00% 0.38869 0.00% 1.719 0.000 1.000 25.24 MB
PHP - perf/in_array 0.38870 0.39072 0.00033 0.08% 0.38923 0.13% 0.38919 0.13% 2.027 -7.649 0.000 25.18 MB

Symfony 2.8.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7a1c261 0.66326 0.66598 0.00046 0.07% 0.66382 0.00% 0.66372 0.00% 2.379 0.000 1.000 25.21 MB
PHP - perf/in_array 0.66389 0.67015 0.00086 0.13% 0.66448 0.10% 0.66432 0.09% 6.010 -6.866 0.000 25.28 MB

Wordpress 6.9 main page - 50 iterations, 20 warmups, 20 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7a1c261 0.58552 0.58889 0.00085 0.15% 0.58641 0.00% 0.58611 0.00% 1.762 0.000 1.000 25.23 MB
PHP - perf/in_array 0.58446 0.59139 0.00114 0.19% 0.58521 -0.20% 0.58492 -0.20% 3.924 7.097 0.000 25.30 MB

bench.php - 50 iterations, 20 warmups, 2 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@7a1c261 0.44448 0.44661 0.00045 0.10% 0.44578 0.00% 0.44585 0.00% -0.697 0.000 1.000 25.23 MB
PHP - perf/in_array 0.44422 0.44691 0.00056 0.13% 0.44566 -0.03% 0.44576 -0.02% -0.355 1.299 0.194 25.30 MB

@jorgsowa

Copy link
Copy Markdown
Contributor Author

Hey @iluuu1994 Is there anything more I can regarding this PR? Should I measure something else?

@github-actions

Copy link
Copy Markdown

AWS x86_64 (c6id.metal)

Attribute Value
Environment aws
Instance type c6id.metal
Architecture x86_64
CPU Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, 64 cores @ 2900 MHz
CPU settings disabled deeper C-states, disabled turbo boost, disabled hyper-threading
RAM 251 GB
Kernel 6.18.38-76.139.amzn2023.x86_64
OS Amazon Linux 2023.12.20260727
GCC 14.2.1
Binary layout strategy bolt-align
Time 2026-09-23 21:38:23 UTC
Job details https://github.com/php/php-src/actions/runs/35923641336 (Artifacts)
Changeset https://github.com/php/php-src/compare/2e20074310..0dea8bf816

Laravel 12.11.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@2e20074 0.39754 0.40040 0.00045 0.11% 0.39799 0.00% 0.39789 0.00% 3.626 0.000 1.000 30.82 MB
PHP - perf/in_array 0.39701 0.40072 0.00054 0.14% 0.39743 -0.14% 0.39733 -0.14% 4.902 7.228 0.000 30.80 MB

Symfony 2.8.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@2e20074 0.68609 0.68936 0.00056 0.08% 0.68688 0.00% 0.68676 0.00% 2.486 0.000 1.000 30.75 MB
PHP - perf/in_array 0.68650 0.68818 0.00040 0.06% 0.68707 0.03% 0.68695 0.03% 1.022 -3.264 0.001 30.74 MB

Wordpress 6.9 main page - 50 iterations, 20 warmups, 20 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@2e20074 0.59549 0.59820 0.00055 0.09% 0.59610 0.00% 0.59597 0.00% 2.131 0.000 1.000 30.76 MB
PHP - perf/in_array 0.59421 0.59599 0.00040 0.07% 0.59483 -0.21% 0.59475 -0.21% 1.089 8.186 0.000 30.75 MB

bench.php - 50 iterations, 20 warmups, 2 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@2e20074 0.45024 0.45215 0.00042 0.09% 0.45102 0.00% 0.45093 0.00% 0.783 0.000 1.000 30.76 MB
PHP - perf/in_array 0.45164 0.46682 0.00203 0.45% 0.45310 0.46% 0.45275 0.40% 6.531 -8.572 0.000 30.75 MB

micro_bench.php - 50 iterations, 5 warmups, 1 request (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@2e20074 1.29728 1.31153 0.00324 0.25% 1.30563 0.00% 1.30539 0.00% -0.129 0.000 1.000 30.76 MB
PHP - perf/in_array 1.29738 1.31420 0.00353 0.27% 1.30581 0.01% 1.30587 0.04% -0.008 -0.238 0.812 30.75 MB

@laruence

Copy link
Copy Markdown
Member

I don't think this optimization is worth it.

The benefit is too small: array_search() gains nothing at all (1.00× in every case), and in_array() only shows a real gain at 500k elements. At 500 elements it's 1.05 ± 0.12× and at 5 it's 1.01 ± 0.23× — both within noise. Arrays with hundreds of thousands of packed elements aren't the common shape for in_array(), so the extreme case isn't very informative.

What I do care about is code size. I compiled ext/standard/array.c at -O2 with and without this change (identical trees otherwise) and the affected functions grow by ~2.4 KB — _php_search_array itself goes from 1484 to 3016 bytes on arm64, and the whole family from 3372 to 5884. Same on x86_64: 3348 to 5713.

The reason is that _php_search_array is outlined (both zif_in_array and zif_array_search tail-call into it), so behavior stays a runtime value and each of the six branches now instantiates three loops instead of one. More code in the hot path means more icache misses, and every caller pays that — including all array_search() users who get no benefit.

I think this is a net loss.

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.

4 participants