-
-
Save Thinkscape/1965669 to your computer and use it in GitHub Desktop.
<?php | |
if(!isset($argv[1])){ | |
echo "Usage: ".$argv[0]." (number of iterations)\n"; | |
exit(1); | |
} | |
/** | |
* Arrays to check | |
*/ | |
$tests = array( | |
array(array( | |
'foo' => 'bar', | |
)), | |
array(array( | |
'bar', | |
'foo' => 'bar', | |
'baz', | |
)), | |
array(null), | |
array(true), | |
array(false), | |
array(0), | |
array(1), | |
array(0.0), | |
array(1.0), | |
array('string'), | |
array(array(0, 1, 2)), | |
array(new stdClass), | |
array_fill(0,1000,uniqid()), // big numeric array | |
array_fill_keys(range(2,1000,3),uniqid()), // big misaligned numeric array (=associative) | |
array_fill_keys( // big associative array | |
str_split( | |
str_repeat(uniqid('',true),100), | |
3 | |
), | |
true | |
) | |
); | |
$iterations = (int)$argv[1]; | |
/** | |
* Common methods to check associative array | |
*/ | |
$methods = array( | |
'method1 (array_values check)' => | |
function($array){ | |
return (array_values($array) !== $array); | |
}, | |
'method2 (array_keys comparison)' => | |
function($array){ | |
$array = array_keys($array); return ($array !== array_keys($array)); | |
}, | |
'method3 (array_filter of keys)' => | |
function($array){ | |
return count(array_filter(array_keys($array), 'is_string')) > 0; | |
} | |
); | |
/** | |
* Perform benchmark on each method | |
*/ | |
foreach($methods as $name=>$func){ | |
echo "Testing $name - $iterations iterations\n"; | |
$time = microtime(true); | |
for($x=0;$x<$iterations;$x++){ | |
foreach($tests as $array){ | |
$func($array); | |
} | |
} | |
/** | |
* Show results | |
*/ | |
$totalTime = (microtime(true) - $time); | |
$avgTime = $totalTime / ($iterations * count($tests)); | |
echo " Total time: ".number_format($totalTime,5,'.',' ')." s\n"; | |
echo " Average : ".number_format($avgTime*1000,5,'.',' ')." ms / test \n"; | |
echo "\n"; | |
} | |
@alixaxel That is correct, it will consume a little bit more memory than method 2 - but that's temporary usage per each fn call (per test) and becomes visible only for flat, long arrays (say 1000+ keys).
Method 3 is what I wanted since it doesn't matter if the array is sequential or not. You can always coerce an array into a sequential one using array_values($array).
I added a method 4 which improves the speed by doing type casting instead of type checking which appears to be much faster.
'method4 (foreach typecast)' =>
function($array){
foreach($array as $key => $value) {
if ($key !== (int) $key) {
return true;
}
}
return false;
},
Testing method1 (array_values check) - 1000 iterations
Total time: 0.40560 s
Average : 0.02704 ms / test
Testing method2 (array_keys comparison) - 1000 iterations
Total time: 0.57720 s
Average : 0.03848 ms / test
Testing method3 (array_filter of keys) - 1000 iterations
Total time: 5.99041 s
Average : 0.39936 ms / test
Testing method4 (foreach typecast) - 1000 iterations
Total time: 0.54600 s
Average : 0.03640 ms / test
Now it is comparable with the first two.
Also, there is no copying of array keys or values so no extra memory consumption. Especially array_values().
In my case I'm saving arrays to storage and schema depends on array type, The array may have blobs of data several Mb in size. So copying all values would be memory expensive. Instead working with indexed arrays I use foreach() so you don't assume keys are sequential.
PHP 7.1.4
Testing method1 (array_values check) - 100000 iterations
Total time: 2.32235 s
Average : 0.00155 ms / test
Testing method2 (array_keys comparison) - 100000 iterations
Total time: 3.17074 s
Average : 0.00211 ms / test
Testing method3 (array_filter of keys) - 100000 iterations
Total time: 6.25823 s
Average : 0.00417 ms / test
From PHPTester
PHP 5.5
Testing method1 (array_values check) - 10000 iterations
Total time: 0.85214 s
Average : 0.00568 ms / test
Testing method2 (array_keys comparison) - 10000 iterations
Total time: 1.57167 s
Average : 0.01048 ms / test
Testing method3 (array_filter of keys) - 10000 iterations
Total time: 2.99962 s
Average : 0.02000 ms / test
Testing method4 (foreach typecast) - 10000 iterations
Total time: 1.11735 s
Average : 0.00745 ms / test
PHP 5.6
Testing method1 (array_values check) - 10000 iterations
Total time: 1.00357 s
Average : 0.00669 ms / test
Testing method2 (array_keys comparison) - 10000 iterations
Total time: 1.77896 s
Average : 0.01186 ms / test
Testing method3 (array_filter of keys) - 10000 iterations
Total time: 3.71037 s
Average : 0.02474 ms / test
Testing method4 (foreach typecast) - 10000 iterations
Total time: 1.34244 s
Average : 0.00895 ms / test
PHP 7.0
Testing method1 (array_values check) - 10000 iterations
Total time: 0.23163 s
Average : 0.00154 ms / test
Testing method2 (array_keys comparison) - 10000 iterations
Total time: 0.30419 s
Average : 0.00203 ms / test
Testing method3 (array_filter of keys) - 10000 iterations
Total time: 0.60409 s
Average : 0.00403 ms / test
Testing method4 (foreach typecast) - 10000 iterations
Total time: 0.56797 s
Average : 0.00379 ms / test
Method 1 is even more faster with PHP 7.2:
Testing method1 (array_values check) - 10000 iterations
Total time: 0.02857 s
Average : 0.00019 ms / test (!)
Testing method2 (array_keys comparison) - 10000 iterations
Total time: 0.19137 s
Average : 0.00128 ms / test
Testing method3 (array_filter of keys) - 10000 iterations
Total time: 0.48394 s
Average : 0.00323 ms / test
Testing method4 (foreach typecast) - 10000 iterations
Total time: 0.41342 s
Average : 0.00276 ms / test
Hi, I just came with an idea of a is_associative_array function if you are still open to contributions:
<?php
function is_associative_array( array &$a ): bool {
$l = count( $a );
for ( $i = 0; $i < $l; $i++ ) {
if ( key( $a ) !== $i ) {
return true;
}
next( $a );
}
return false;
}
Tell me about it!
Hi, I just came with an idea of a is_associative_array function if you are still open to contributions:
<?php function is_associative_array( array &$a ): bool { $l = count( $a ); for ( $i = 0; $i < $l; $i++ ) { if ( key( $a ) !== $i ) { return true; } next( $a ); } return false; }
Tell me about it!
i just tested your method and it is way worse than all others... sorry :)
source: https://gist.github.com/oriadam/5e8424608fb3f8d70ec3e9ae344dbf8f
Testing 10000 iterations on PHP version 7.3.25
if (array_key_first($array)!==0) return true; return array_values($array) !== $array;
Total time: 0.26179 s
Average : 0.00175 ms / test
return array_values($array) !== $array;
Total time: 0.23978 s
Average : 0.00160 ms / test
return range(0,count($array)) !== array_keys($array);
Total time: 0.44664 s
Average : 0.00298 ms / test
return array_keys($array); return ($array !== array_keys($array));
Total time: 0.50882 s
Average : 0.00339 ms / test
return count(array_filter(array_keys($array), "is_string")) > 0;
Total time: 3.99202 s
Average : 0.02661 ms / test
foreach...$i++
Total time: 1.23879 s
Average : 0.00826 ms / test
for...key...next
Total time: 7.73038 s
Average : 0.05154 ms / test
check this out:
$methods = [
// [...]
'method4 (revision of method1, with php8.1 fn)' =>
function ($array) {
return (array_is_list($array) === false);
},
'method5 (json_encode output parsing with array_is_list - php>=8.1)' =>
static function ($array) {
// consider empty, and [0, 1, 2, ...] sequential
if (empty($array) || array_is_list($array)) {
return false;
}
// first scenario:
// [ 1 => [*any*] ]
// [ 'a' => [*any*] ]
foreach ( $array as $key => $value ) {
if (is_array($value)) {
return true;
}
}
// second scenario: read the json string
$jsonNest = json_encode($array, JSON_THROW_ON_ERROR);
return str_contains($jsonNest, '{');
},
'method6 (json_encode output parsing - php=8.0)' =>
static function ($array) {
if (empty($array) || array_values($array) !== $array) { return false; }
foreach ( $array as $key => $value ) { if (is_array($value)) { return true; } }
$jsonNest = json_encode($array, JSON_THROW_ON_ERROR);
return str_contains($jsonNest, '{');
},
];
Results
Testing method1 (array_values check) - 10000 iterations
Total time: 0.60444 s
Average : 0.00403 ms / test
Testing method2 (array_keys comparison) - 10000 iterations
Total time: 0.32689 s
Average : 0.00218 ms / test
Testing method3 (array_filter of keys) - 10000 iterations
Total time: 2.22523 s
Average : 0.01483 ms / test
Testing method4 (revision of method1, with php8.1 fn) - 10000 iterations
Total time: 0.09834 s
Average : 0.00066 ms / test
Testing method5 (json_encode output parsing with array_is_list - php>=8.1) - 10000 iterations
Total time: 0.47469 s
Average : 0.00316 ms / test
Testing method6 (json_encode output parsing - php<=8.0) - 10000 iterations
Total time: 1.01134 s
Average : 0.00674 ms / test
Anyone curious about the benchmark without the static closure?
Testing method5 (json_encode output parsing with array_is_list - php>=8.1) - 10000 iterations
Total time: 1.08914 s
Average : 0.00726 ms / test
Testing method6 (json_encode output parsing - php=8.0) - 10000 iterations
Total time: 4.92953 s
Average : 0.03286 ms / test
They are much slower!
other methods perform the same instead. Somebody can explain me why?
Beware that the fastest method should consume more memory while the GC doesn't kicks in.