web-dev-qa-db-fra.com

Liste des Big-O pour PHP les fonctions

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.

317
Kendall Hopkins

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:

  1. isset/array_key_exists est beaucoup plus rapide que in_array et array_search
  2. + (union) est un peu plus rapide que array_merge (et plus joli). Mais cela fonctionne différemment alors gardez cela à l'esprit.
  3. shuffle est sur le même niveau Big-O que array_Rand
  4. array_pop/array_Push est plus rapide que array_shift/array_unshift en raison d'une pénalité de réindexation

Recherches:

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).

php array lookup graph

$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 );
}
568
Kendall Hopkins

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 secondes
array_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é.

3
ryeguy

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 .

2
Dathan

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.

0
Josh Stern