Après avoir utilisé PHP pendant un moment, j'ai remarqué que toutes les fonctions intégrées PHP ne sont pas aussi rapides que prévu. Considérez ces deux implémentations possibles d'une fonction qui recherche si un nombre est premier en utilisant un tableau mis en cache de nombres premiers.
//very slow for large $prime_array
$prime_array = array( 2, 3, 5, 7, 11, 13, .... 104729, ... );
$result_array = array();
foreach( $prime_array => $number ) {
$result_array[$number] = in_array( $number, $large_prime_array );
}
//speed is much less dependent on size of $prime_array, and runs much faster.
$prime_array => array( 2 => NULL, 3 => NULL, 5 => NULL, 7 => NULL,
11 => NULL, 13 => NULL, .... 104729 => NULL, ... );
foreach( $prime_array => $number ) {
$result_array[$number] = array_key_exists( $number, $large_prime_array );
}
En effet, in_array
est implémenté avec une recherche linéaire O(n) qui ralentira linéairement à mesure que $prime_array
augmentera. Où la fonction array_key_exists
est implémentée avec une recherche de hachage O(1) qui ne ralentira pas tant que la table de hachage ne sera pas extrêmement remplie (dans ce cas, il s'agira uniquement de O (n)).
Jusqu'ici, j'ai dû découvrir les gros-O par essais et erreurs, et occasionnellement en regardant le code source . Maintenant pour la question ...
Existe-t-il une liste des grandes heures théoriques (ou pratiques) pour toutes * les fonctions intégrées PHP?
* ou du moins les intéressants
Par exemple, je trouve très difficile de prédire le grand O des fonctions répertoriées, car l’implémentation possible dépend de structures de données de base inconnues de PHP: array_merge
, array_merge_recursive
, array_reverse
, array_intersect
, array_combine
, str_replace
(avec des entrées de tableau), etc.
Comme il semble que personne ne l’ait fait avant, je pensais que ce serait une bonne idée de l’avoir comme référence quelque part. Je suis allé cependant et soit via benchmark ou code-skimming pour caractériser les fonctions array_*
. J'ai essayé de placer le Big-O plus intéressant près du sommet. Cette liste n'est pas complète.
Remarque: Tous les Big-O où calculé en supposant une recherche de hachage est O(1) même si c'est vraiment O (n). Le coefficient de n est si bas que le temps système de stockage pour stocker un ensemble assez grand vous ferait mal avant que les caractéristiques de la recherche Big-O ne commencent à prendre effet. Par exemple, la différence entre un appel à array_key_exists
à N = 1 et N = 1 000 000 correspond à une augmentation de temps d'environ 50%.
Points intéressants:
isset
/array_key_exists
est beaucoup plus rapide que in_array
et array_search
+
(union) est un peu plus rapide que array_merge
(et plus joli). Mais cela fonctionne différemment alors gardez cela à l'esprit.shuffle
est sur le même niveau Big-O que array_Rand
array_pop
/array_Push
est plus rapide que array_shift
/array_unshift
en raison d'une pénalité de réindexationRecherches:
array_key_exists
O(n), mais très proche de O(1), en raison de l'interrogation linéaire dans les collisions, mais du fait que le risque de collision est très faible, le coefficient est également très faible. Je trouve que vous traitez les recherches de hachage comme O(1) pour donner un big-O plus réaliste. Par exemple, la différence entre N = 1000 et N = 100000 ne ralentit que de 50% environ.
isset( $array[$index] )
O(n) mais très proche de O(1) - il utilise la même recherche que array_key_exists. Dans la mesure où il s'agit d'une construction de langage, la recherche sera mise en cache si la clé est codée en dur, ce qui accélère les opérations dans les cas où la même clé est utilisée à plusieurs reprises.
in_array
O(n) - c'est parce qu'il effectue une recherche linéaire dans le tableau jusqu'à ce qu'il trouve la valeur.
array_search
O(n) - il utilise la même fonction principale que in_array mais renvoie la valeur.
Fonctions de file d'attente:
array_Push
O (∑ var_i, pour tout i)
array_pop
O(1)
array_shift
O(n) - il doit réindexer toutes les clés
array_unshift
O (n + ∑ var_i, pour tout i) - il doit réindexer toutes les clés
Intersection du tableau, Union, Soustraction:
array_intersect_key
si l'intersection 100% fait O (Max (param_i_size) * ∑param_i_count, pour tout i), si l'intersection 0% intersecte O (param_i_size, pour tout i)
array_intersect
si l'intersection 100% fait O (n ^ 2 * ∑param_i_count, pour tout i), si l'intersection 0% intersecte O (n ^ 2)
array_intersect_assoc
si l'intersection 100% fait O (Max (param_i_size) * ∑param_i_count, pour tout i), si l'intersection 0% intersecte O (param_i_size, pour tout i)
array_diff
O (π param_i_size, pour tout i) - C'est le produit de tous les param_sizes
array_diff_key
O (∑ param_i_size, pour i! = 1) - car nous n'avons pas besoin de parcourir le premier tableau.
array_merge
O (∑ array_i, i! = 1) - n'a pas besoin de parcourir le premier tableau
+
(union) O (n), où n est la taille du deuxième tableau (c'est-à-dire array_first + array_second) - moins de temps système que array_merge puisqu'il n'est pas nécessaire de le renuméroter
array_replace
O (∑ tableau_i, pour tout i)
Aléatoire:
shuffle
O (n)
array_Rand
O(n) - Requiert une interrogation linéaire.
Big-O évident:
array_fill
O (n)
array_fill_keys
O (n)
range
O (n)
array_splice
O (décalage + longueur)
array_slice
O (décalage + longueur) ou O(n) si longueur = NULL
array_keys
O (n)
array_values
O (n)
array_reverse
O (n)
array_pad
O (pad_size)
array_flip
O (n)
array_sum
O (n)
array_product
O (n)
array_reduce
O (n)
array_filter
O (n)
array_map
O (n)
array_chunk
O(n)
array_combine
O (n)
Je voudrais remercier Eureqa d’avoir facilité la recherche du Big-O des fonctions. C'est un incroyable programme {gratuit} qui permet de trouver la meilleure fonction d'adaptation pour des données arbitraires.
MODIFIER:
Pour ceux qui doutent que les recherches sur les tableaux PHP soient O(N)
, j'ai écrit un point de repère pour le tester (elles sont toujours effectivement O(1)
pour les valeurs les plus réalistes).
$tests = 1000000;
$max = 5000001;
for( $i = 1; $i <= $max; $i += 10000 ) {
//create lookup array
$array = array_fill( 0, $i, NULL );
//build test indexes
$test_indexes = array();
for( $j = 0; $j < $tests; $j++ ) {
$test_indexes[] = Rand( 0, $i-1 );
}
//benchmark array lookups
$start = microtime( TRUE );
foreach( $test_indexes as $test_index ) {
$value = $array[ $test_index ];
unset( $value );
}
$stop = microtime( TRUE );
unset( $array, $test_indexes, $test_index );
printf( "%d,%1.15f\n", $i, $stop - $start ); //time per 1mil lookups
unset( $stop, $start );
}
Vous voulez presque toujours utiliser isset
au lieu de array_key_exists
. Je ne regarde pas les éléments internes, mais je suis à peu près sûr que array_key_exists
est O(N) car il itère sur chaque clé du tableau, alors que isset
tente d'accéder à l'élément en utilisant le même algorithme de hachage qui est utilisé lorsque vous accédez à un index de tableau. Cela devrait être O (1).
Un "Gotcha" à surveiller est la suivante:
$search_array = array('first' => null, 'second' => 4);
// returns false
isset($search_array['first']);
// returns true
array_key_exists('first', $search_array);
J'étais curieux, alors j'ai comparé la différence:
<?php
$bigArray = range(1,100000);
$iterations = 1000000;
$start = microtime(true);
while ($iterations--)
{
isset($bigArray[50000]);
}
echo 'is_set:', microtime(true) - $start, ' seconds', '<br>';
$iterations = 1000000;
$start = microtime(true);
while ($iterations--)
{
array_key_exists(50000, $bigArray);
}
echo 'array_key_exists:', microtime(true) - $start, ' seconds';
?>
is_set:
0.132308959961 secondesarray_key_exists:
2.33202195168 secondes
Bien sûr, cela ne montre pas la complexité temporelle, mais montre comment les 2 fonctions se comparent.
Pour tester la complexité temporelle, comparez le temps nécessaire à l'exécution de l'une de ces fonctions sur la première et la dernière clé.
L'explication du cas que vous décrivez spécifiquement est que les tableaux associatifs sont implémentés sous forme de tables de hachage - la recherche par clé (et, par conséquent, array_key_exists
) vaut 0 (1). Cependant, les tableaux n'étant pas indexés par valeur, le seul moyen de déterminer si une valeur existe dans le tableau consiste en une recherche linéaire. Il n'y a pas de surprise là-bas.
Je ne pense pas qu'il existe une documentation complète spécifique de la complexité algorithmique des méthodes PHP. Cependant, si le problème est suffisamment important pour justifier un effort, vous pouvez toujours regarder à travers le code source .
Si des personnes rencontraient des problèmes dans la pratique lors de collisions clés, elles implémenteraient des conteneurs avec une recherche secondaire ou une arborescence équilibrée. L'arbre équilibré donnerait à O (log n) le comportement dans le cas le plus défavorable et à O(1) moy. cas (le hash lui-même). La surcharge ne vaut pas la peine dans la plupart des applications pratiques de la mémoire, mais il existe peut-être des bases de données qui implémentent cette forme de stratégie mixte comme cas par défaut.