Après avoir utilisé PHP pendant un certain temps maintenant, j'ai remarqué que toutes les fonctions PHP intégrées ne sont pas aussi rapides que prévu. Considérez ces deux implémentations possibles d'une fonction qui trouve si un nombre est premier en utilisant un tableau de nombres premiers mis en cache.
//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, il in_array
est implémenté avec une recherche linéaire O (n) qui ralentira linéairement à mesure qu'il $prime_array
grandit. Où la array_key_exists
fonction est implémentée avec une recherche de hachage O (1) qui ne ralentira pas à moins que la table de hachage ne soit extrêmement remplie (auquel cas ce n'est que O (n)).
Jusqu'à présent, j'ai dû découvrir les big-O via des essais et des erreurs, et parfois regarder le code source . Maintenant pour la question ...
Existe-t-il une liste des grands temps O théoriques (ou pratiques) pour toutes * les fonctions PHP intégrées?
* ou du moins les plus intéressants
Par exemple, je trouve qu'il est très difficile de prédire le grand O des fonctions énumérées parce que la mise en œuvre possible dépend des structures de données de base inconnue de PHP: array_merge
, array_merge_recursive
, array_reverse
, array_intersect
, array_combine
, str_replace
(avec des entrées de tableau), etc.
true
, puis de tester la présence en utilisantisset($large_prime_array[$number])
. Si je me souviens bien, c'est dans l'ordre d'être des centaines de fois plus rapide que lain_array
fonction.array_key_exists
, je compare àin_array
.in_array
itère chaque élément du tableau et compare la valeur à l'aiguille que vous lui passez. Si vous retournez les valeurs à la clé (et remplacez simplement chacune des valeurs par une valeur fictive commetrue
, l'utilisationisset
est beaucoup plus rapide. En effet, les clés d'un tableau sont indexées par PHP (comme une table de hachage). Par conséquent, la recherche une matrice de cette manière peut avoir une amélioration significative de la vitesse.Réponses:
Comme il ne semble pas que quelqu'un l'ait fait avant, je pensais que ce serait une bonne idée de l'avoir pour référence quelque part. Je suis allé cependant et soit par référence ou par écrémage de code pour caractériser les
array_*
fonctions. J'ai essayé de placer le Big-O le plus intéressant près du sommet. Cette liste n'est pas complète.Remarque: Tous les Big-O sont calculés en supposant qu'une recherche de hachage est O (1) même si c'est vraiment O (n). Le coefficient du n est si bas que le surcoût de stockage d'un stockage suffisamment grand vous nuirait 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 est une augmentation de temps de ~ 50%.Points intéressants :
isset
/array_key_exists
est beaucoup plus rapide quein_array
etarray_search
+
(union) est un peu plus rapide quearray_merge
(et semble plus joli). Mais cela fonctionne différemment, gardez cela à l'esprit.shuffle
est sur le même niveau Big-O quearray_rand
array_pop
/array_push
est plus rapide quearray_shift
/ enarray_unshift
raison d'une pénalité de réindexationRecherches :
array_key_exists
O (n) mais très proche de O (1) - c'est à cause de l'interrogation linéaire dans les collisions, mais parce que le risque de collisions 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 n'est que d'environ 50% de ralentissement.isset( $array[$index] )
O (n) mais très proche de O (1) - il utilise la même recherche que array_key_exists. Étant donné que c'est une construction de langage, mettra en cache la recherche si la clé est codée en dur, ce qui entraîne une accélération 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 une 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ésarray_unshift
O (n + ∑ var_i, pour tout i) - il doit réindexer toutes les clésIntersection, union, soustraction de tableau :
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 toutes les param_sizesarray_diff_key
O (∑ param_i_size, pour i! = 1) - c'est parce que nous n'avons pas besoin d'itérer sur le premier tableau.array_merge
O (∑ array_i, i! = 1) - n'a pas besoin d'itérer sur le premier tableau+
(union) O (n), où n est la taille du 2e tableau (c'est-à-dire array_first + array_second) - moins de surcharge que array_merge car il n'a pas besoin de renuméroterarray_replace
O (∑ array_i, pour tout i)Aléatoire :
shuffle
Sur)array_rand
O (n) - Nécessite un sondage linéaire.Big-O évident :
array_fill
Sur)array_fill_keys
Sur)range
Sur)array_splice
O (décalage + longueur)array_slice
O (décalage + longueur) ou O (n) si longueur = NULLarray_keys
Sur)array_values
Sur)array_reverse
Sur)array_pad
O (pad_size)array_flip
Sur)array_sum
Sur)array_product
Sur)array_reduce
Sur)array_filter
Sur)array_map
Sur)array_chunk
Sur)array_combine
Sur)Je tiens à remercier Eureqa pour avoir facilité la recherche du Big-O des fonctions. C'est un programme gratuit incroyable qui peut trouver la meilleure fonction d'ajustement pour des données arbitraires.
ÉDITER:
Pour ceux qui doutent que les recherches de tableaux PHP le soient
O(N)
, j'ai écrit un benchmark pour le tester (elles sont toujours efficacesO(1)
pour les valeurs les plus réalistes).la source
L'explication du cas que vous décrivez spécifiquement est que les tableaux associatifs sont implémentés comme des tables de hachage - donc la recherche par clé (et en conséquence,
array_key_exists
) est O (1). Cependant, les tableaux ne sont pas indexés par valeur, donc la seule façon dans le cas général de découvrir si une valeur existe dans le tableau est une recherche linéaire. Il n'y a pas de surprise là-bas.Je ne pense pas qu'il existe une documentation complète et spécifique de la complexité algorithmique des méthodes PHP. Cependant, si c'est une préoccupation suffisamment importante pour justifier l'effort, vous pouvez toujours parcourir le code source .
la source
Vous voulez presque toujours utiliser
isset
au lieu dearray_key_exists
. Je ne regarde pas les internes, mais je suis sûr quearray_key_exists
c'est O (N) car il itère sur chaque clé du tableau, tout enisset
essayant 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 "piège" à surveiller est le suivant:
J'étais curieux, j'ai donc comparé la différence:
is_set:
0.132308959961 secondesarray_key_exists:
2.33202195168 secondesBien sûr, cela ne montre pas la complexité temporelle, mais cela 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 clé et la dernière clé.
la source
$arrray[] = $append
vs.array_push($array, $append)
Deuxièmement, array_key_exists différencie également les valeurs non définies et nulles. Pour$a = array('fred' => null);
array_key_exists('fred', $a)
retournera vrai tandis queisset($['fred'])
retournera faux. Cette étape supplémentaire n'est pas anodine et augmentera considérablement le temps d'exécution.Si les gens rencontraient des problèmes dans la pratique avec des collisions clés, ils implémenteraient des conteneurs avec une recherche de hachage secondaire ou un arbre équilibré. L'arbre équilibré donnerait le comportement O (log n) dans le pire des cas et O (1) moy. cas (le hachage lui-même). Le surcoût n'en vaut pas la peine dans la plupart des applications de 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.
la source