In this article, we are going to find out the fastest PHP function that is used to check if an array contains a value. We will test and compare the performance of five different methods. We will analyze how the parameter $strict affects the performance of array_search and in_array. And finally, we will compare the results with and without OPcache enabled, and draw conclusions, which method is better.
How we will test
We will look for a presence of a specific search string in a simple one-dimensional non-associative array of strings. We will perform our tests on three input arrays of different sizes (100, 1000 and 10,000 elements) to see if performance of these methods degrade with increasing number of elements.
To make our results more independent on current server load, we'll perform 3 passes for 100,000 iterations for every search method.
In order to check how the performance is affected by OPCache, we run our testing script twice: with OPcache disabled and with OPCache enabled. We disable OPcache for the testing script by adding its path into the OPcache blacklist.
Functions we compare
Here is a list of all array searching methods we'll compare (if there is something not on the list, write in comment):
- array_search
- array_search with $strict = true
- in_array
- in_array with $strict = true
- isset (isset($arr[$needle]))
- array value by key with the at sign (@) error suppression operator (@$arr[$needle])
- array value by key with the (??) null coalescing operator ($arr[$needle] ?? 0)
Our test environment
Server with the Intel Xeon E3 1270 V3 CPU running PHP 7.3.24.
Results with OPcache disabled
Array size: 100, 3 passes of 100,000 iterations
Method | Total time, seconds* | Preparation time, seconds | Operations/s |
---|---|---|---|
isset | 0.0065479 | 0.0000162 | 45,929,742 |
null coalescing | 0.0088511 +35% | 0.0000069 | 33,920,776 -26% |
array_search | 0.1349378 +1,425% | 0 | 2,223,247 -93% |
in_array | 0.1424952 +6% | 0 | 2,105,335 -5% |
error suppression | 0.1596813 +12% | 0.0000093 | 1,878,851 -11% |
array_search strict | 0.1943438 +22% | 0 | 1,543,656 -18% |
in_array strict | 0.1973531 +2% | 0 | 1,520,118 -2% |
Array size: 1000, 3 passes of 100,000 iterations
Method | Total time, seconds* | Preparation time, seconds | Operations/s |
---|---|---|---|
isset | 0.0064571 | 0.0001051 | 47,229,607 |
null coalescing | 0.0086432 +34% | 0.0000582 | 34,944,768 -26% |
error suppression | 0.1587451 +1,737% | 0.0000641 | 1,890,587 -95% |
array_search | 1.3462260 +748% | 0 | 222,845 -88% |
in_array | 1.4144890 +5% | 0 | 212,091 -5% |
array_search strict | 1.8396320 +30% | 0 | 163,076 -23% |
in_array strict | 1.8466282 +0% | 0 | 162,458 -0% |
Array size: 10000, 3 passes of 100,000 iterations
Method | Total time, seconds* | Preparation time, seconds | Operations/s |
---|---|---|---|
isset | 0.0073812 | 0.0009792 | 46,860,241 |
null coalescing | 0.0096066 +30% | 0.0006218 | 33,389,709 -29% |
error suppression | 0.1587968 +1,553% | 0.0006196 | 1,896,608 -94% |
in_array | 14.8727291 +9,266% | 0 | 20,171 -99% |
array_search | 15.0306778 +1% | 0 | 19,959 -1% |
in_array strict | 18.9210389 +26% | 0 | 15,855 -21% |
array_search strict | 19.1551011 +1% | 0 | 15,662 -1% |
* For the "isset", "null coalescing" and "error suppression" methods the total time includes time of array_flip call to prepare data.
Results with OPcache enabled
Array size: 100, 3 passes of 100,000 iterations
Method | Total time, seconds* | Preparation time, seconds | Operations/s |
---|---|---|---|
isset | 0.0058002 | 0.0000191 | 51,892,577 |
null coalescing | 0.0078719 +36% | 0.0000060 | 38,139,282 -27% |
array_search | 0.1314380 +1,570% | 0 | 2,282,445 -94% |
in_array | 0.1400669 +7% | 0 | 2,141,834 -6% |
error suppression | 0.1493380 +7% | 0.0000081 | 2,008,975 -6% |
in_array strict | 0.1880350 +26% | 0 | 1,595,448 -21% |
array_search strict | 0.1889241 +0% | 0 | 1,587,940 -0% |
Array size: 1000, 3 passes of 100,000 iterations
Method | Total time, seconds* | Preparation time, seconds | Operations/s |
---|---|---|---|
isset | 0.0057838 | 0.0001268 | 53,032,039 |
null coalescing | 0.0078449 +36% | 0.0000672 | 38,571,859 -27% |
error suppression | 0.1482992 +1,790% | 0.0000648 | 2,023,822 -95% |
array_search | 1.3640051 +820% | 0 | 219,941 -89% |
in_array | 1.4514329 +6% | 0 | 206,692 -6% |
array_search strict | 1.8645689 +28% | 0 | 160,895 -22% |
in_array strict | 1.8645763 +0% | 0 | 160,894 -0% |
Array size: 10000, 3 passes of 100,000 iterations
Method | Total time, seconds* | Preparation time, seconds | Operations/s |
---|---|---|---|
isset | 0.0068820 | 0.0011969 | 52,769,604 |
null coalescing | 0.0087378 +27% | 0.0008657 | 38,109,250 -28% |
error suppression | 0.1490369 +1,606% | 0.0008991 | 2,025,141 -95% |
in_array | 15.0471058 +9,996% | 0 | 19,937 -99% |
array_search | 15.2325349 +1% | 0 | 19,695 -1% |
in_array strict | 19.2085185 +26% | 0 | 15,618 -21% |
array_search strict | 19.3677390 +1% | 0 | 15,490 -1% |
* For the "isset", "null coalescing" and "error suppression" methods the total time includes time of array_flip call to prepare data.
Analysis of results
As we can see, PHP today is a very capable language. Every function that we tested runs very fast, performing millions of calls per second. This means that you don't need to rewrite your code if you have a few calls of any of these functions. But, if your script performs thousands of array searches, it is preferable to use the most performance-efficient method.
Looking at the results, we can say that:
- isset is a clear winner here. Even with the additional array_flip call (our input array was a non-associative one) it is faster than specialized array search functions.
- Checking array value by key with the null coalescing operator ($arr[$needle] ?? 0) has a bit slower performance because of the overhead on accessing actual value. But this function is a life-saver if you need to check if key exists and get the value at the same time.
- The performance of array_search is acceptable on smaller arrays and drastically drops with increasing array size.
- array_search seems to be a slightly faster than in_array on smaller arrays, but for an array with 10,000 elements we can see that they are on par.
- The array_search and in_array with $strict = true parameter are the slowest methods in our test. This parameter enables a strict type comparison of each element and consumes an additional time. Do not use it unless necessary.
- The "silence" or "shut-op" operator @ should be avoided too. Although this method is faster than array_search and in_array on larger arrays, getting an array value by key if this key is not exists is about 20 times slower with this method than with the null coalescing operator.
The impact of array size
The search array size has almost no impact on the performance of functions that access values by key (isset, null coalescing operator). On the other hand, we see a huge drop of perfomance of array_search and in_array on larger arrays.
The impact of OPcache
OPcache gives a performance gain of about 13% for isset and has almost no influence on the results of array_search and in_array functions.
Test script code
<?php
error_reporting(E_ALL);
set_time_limit(300); //5 min
//nocache
header('X-Accel-Buffering: no');
header("Cache-Control: private");
?>
<html>
<head>
<style>
.results > table th
{
text-align:left;
font-weight: normal;
background: #ddd;
}
.results > table, .results > table td, .results > table th
{
border:1px solid #e7e7e7;
border-collapse:collapse;
margin:0;
padding:0;
}
.results > table td, .results > table th
{
padding: 10px;
}
</style>
<title>Performance test for array search</title></head><body>
<?php
echo date('r').', PHP: '.PHP_VERSION.', OS: '.(defined('PHP_OS_FAMILY') ? PHP_OS_FAMILY : PHP_OS).', ';
$opstatus = function_exists('opcache_get_status') ? opcache_get_status() : 0;
if ($opstatus)
if (!isset($opstatus['scripts'][__FILE__]))
$opstatus = 0;
if ($opstatus)
echo 'OPcache enabled';
else
echo 'OPcache disabled';
echo "<hr>\r\n<div class=\"results\">";
$passes = 3;
$array_sizes = [100,1000,10000];
$icount = 100000;
$tests = ['array_search','array_search strict','in_array','in_array strict',
'isset','error suppression','null coalescing'];
$needle = 'abcd123';
$preparetimes = [];
$times = [];
$fliptime = [];
for ($n = 0; $n < $passes; $n++)
{
foreach ($array_sizes as $asize)
{
$arr = [];
for ($i = 0; $i < $asize; $i++)
$arr[] = sprintf('%04x',rand(0,0xfffffff));
foreach ($tests as $test)
{
$starttime = microtime(true);
$preparetime = 0;
$f = str_replace(' ','_',$test).'_Test';
call_user_func($f);
$times[$asize][$test][] = microtime(true) - $starttime;
if ($preparetime)
$preparetimes[$asize][$test][] = $preparetime - $starttime;
}
}
}
//calc total time
foreach ($times as $asize=>&$tests)
foreach ($tests as &$results)
$results = array_sum($results);
unset($tests, $results);
foreach ($preparetimes as $asize=>&$tests)
foreach ($tests as &$results)
$results = array_sum($results);
unset($tests,$results);
//display results
foreach ($times as $asize=>$tests)
{
asort($tests);
$s = '';
$prevtime = 0;
$prevops = 0;
foreach ($tests as $n=>$t)
{
$preparetime = $preparetimes[$asize][$n] ?? 0;
$timediff = !$prevtime ? '' : ' <sup style="color:red">+'.number_format(($t-$prevtime)/$prevtime*100).'%</sup>';
$optime = $t - $preparetime;
$ops = $passes*$icount/$optime;
$opsdiff = !$prevops ? '' : ' <sup style="color:red">-'.number_format(($prevops-$ops)/$prevops*100).'%</sup>';
if ($preparetime)
$preparetime = number_format($preparetime,7);
$s .= "<tr><td>$n</td><td>".number_format($t,7).$timediff.
"</td><td>$preparetime</td><td>".number_format($ops).$opsdiff.'</td></tr>';
$prevtime = $t;
$prevops = $ops;
}
echo "<h3>Array size: $asize, $passes passes of ".number_format($icount)." iterations</h3>
<table><tr><th>Method</th><th>Total time, seconds*</th>
<th>Preparation time, seconds</th><th>Operations/s</th></tr>$s</table>\r\n";
}
echo '<p>* For the "isset", "null coalescing" and "error suppression" methods the total time includes time of <samp>array_flip</samp> call to prepare data.</p>';
function array_search_Test()
{
global $arr, $icount, $needle;
$c = 0;
for ($i = 0; $i < $icount; $i++)
if (array_search($needle,$arr))
$c++;
return $c;
}
function array_search_strict_Test()
{
global $arr, $icount, $needle;
$c = 0;
for ($i = 0; $i < $icount; $i++)
if (array_search($needle,$arr,true))
$c++;
return $c;
}
function in_array_Test()
{
global $arr, $icount, $needle;
$c = 0;
for ($i = 0; $i < $icount; $i++)
if (in_array($needle,$arr))
$c++;
return $c;
}
function in_array_strict_Test()
{
global $arr, $icount, $needle;
$c = 0;
for ($i = 0; $i < $icount; $i++)
if (in_array($needle,$arr,true))
$c++;
return $c;
}
function isset_Test()
{
global $arr, $icount, $needle, $preparetime;
$newarr = array_flip($arr);
$preparetime = microtime(true);
$c = 0;
for ($i = 0; $i < $icount; $i++)
if (isset($newarr[$needle]))
$c++;
return $c;
}
function error_suppression_Test()
{
global $arr, $icount, $needle, $preparetime;
$newarr = array_flip($arr);
$preparetime = microtime(true);
$c = 0;
for ($i = 0; $i < $icount; $i++)
if (@$newarr[$needle] !== null)
$c++;
return $c;
}
function null_coalescing_Test()
{
global $arr, $icount, $needle, $preparetime;
$newarr = array_flip($arr);
$preparetime = microtime(true);
$c = 0;
for ($i = 0; $i < $icount; $i++)
if ($newarr[$needle] ?? null !== null)
$c++;
return $c;
}
?>
</div></body></html>
Conclusion
All of the reviewed methods are really fast resulting in millions operations per second. This means that you don't need to do any excessive optimization if your array search ratio is low. If you have a few calls to array_search or in_array and your arrays are small, never mind.
On the other hand, If your scripts perform a large amount of array searching and accessing operations or your arrays consist of thousands of elements, consider switching to associative arrays and isset or null coalescing operator, as they are the clear winners of this performance competition.