Nachdem ich PHP für eine Weile verwendet habe, habe ich festgestellt, dass nicht alle integrierten PHP-Funktionen so schnell sind wie erwartet. Betrachten Sie diese beiden möglichen Implementierungen einer Funktion, die mithilfe eines zwischengespeicherten Arrays von Primzahlen ermittelt, ob eine Zahl eine Primzahl ist.
//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 );
}
Dies liegt daran in_array
, dass eine lineare Suche O (n) implementiert wird, die sich mit zunehmendem Wachstum linear verlangsamt $prime_array
. Wenn die array_key_exists
Funktion mit einer Hash-Suche O (1) implementiert wird, die nicht verlangsamt wird, es sei denn, die Hash-Tabelle wird extrem gefüllt (in diesem Fall ist es nur O (n)).
Bisher musste ich die Big-O's durch Ausprobieren entdecken und gelegentlich den Quellcode betrachten . Nun zur Frage ...
Gibt es eine Liste der theoretischen (oder praktischen) großen O-Zeiten für alle * integrierten PHP-Funktionen?
* oder zumindest die interessanten
Zum Beispiel habe ich es sehr schwer , die große O Funktionen aufgelistet vorherzusagen , weil die mögliche Implementierung auf unbekannte Kerndatenstrukturen von PHP ab: array_merge
, array_merge_recursive
, array_reverse
, array_intersect
, array_combine
, str_replace
(mit Array - Eingänge), usw.
true
und dann die Anwesenheit mit zu testenisset($large_prime_array[$number])
. Wenn ich mich richtig erinnere, ist es in der Größenordnung hunderte Male schneller als diein_array
Funktion.array_key_exists
, ich vergleiche mitin_array
.in_array
iteriert jedes Element im Array und vergleicht den Wert mit der Nadel, die Sie an das Array übergeben. Wenn Sie die Werte auf den Schlüssel drehen (und einfach jeden der Werte durch einen Dummy-Wert wie ersetzentrue
, ist die Verwendungisset
um ein Vielfaches schneller. Dies liegt daran, dass die Schlüssel eines Arrays von PHP indiziert werden (wie eine Hashtabelle). Folglich wird gesucht Ein Array auf diese Weise kann eine signifikante Verbesserung der Geschwindigkeit haben.Antworten:
Da es so aussieht, als hätte dies noch niemand getan, dachte ich, es wäre eine gute Idee, es irgendwo als Referenz zu haben. Ich bin jedoch gegangen und entweder über Benchmark oder Code-Skimming, um die
array_*
Funktionen zu charakterisieren . Ich habe versucht, das interessantere Big-O ganz oben zu platzieren. Diese Liste ist nicht vollständig.Hinweis: Alle Big-O-Werte, die unter der Annahme einer Hash-Suche berechnet wurden, sind O (1), obwohl es sich tatsächlich um O (n) handelt. Der Koeffizient von n ist so niedrig, dass der RAM-Overhead beim Speichern eines ausreichend großen Arrays Sie verletzen würde, bevor die Eigenschaften von Lookup Big-O wirksam werden. Zum Beispiel
array_key_exists
beträgt die Differenz zwischen einem Anruf bei N = 1 und N = 1.000.000 ~ 50% Zeitzunahme.Interessante Punkte :
isset
/array_key_exists
Ist viel schneller alsin_array
undarray_search
+
(union) ist etwas schneller alsarray_merge
(und sieht besser aus). Aber es funktioniert anders, denken Sie daran.shuffle
ist auf der gleichen Big-O-Ebene wiearray_rand
array_pop
/array_push
ist schneller alsarray_shift
/array_unshift
aufgrund einer NeuindizierungsstrafeLookups :
array_key_exists
O (n), aber sehr nahe an O (1) - dies liegt an der linearen Abfrage bei Kollisionen, aber da die Wahrscheinlichkeit von Kollisionen sehr gering ist, ist der Koeffizient auch sehr klein. Ich finde, Sie behandeln Hash-Lookups als O (1), um ein realistischeres Big-O zu erhalten. Zum Beispiel ist der Unterschied zwischen N = 1000 und N = 100000 nur etwa 50% langsamer.isset( $array[$index] )
O (n), aber sehr nahe an O (1) - es verwendet dieselbe Suche wie array_key_exists. Da es sich um ein Sprachkonstrukt handelt, wird die Suche zwischengespeichert, wenn der Schlüssel fest codiert ist, was in Fällen, in denen derselbe Schlüssel wiederholt verwendet wird, zu einer Beschleunigung führt.in_array
O (n) - Dies liegt daran, dass eine lineare Suche durch das Array durchgeführt wird, bis der Wert gefunden wird.array_search
O (n) - Es verwendet dieselbe Kernfunktion wie in_array, gibt jedoch einen Wert zurück.Warteschlangenfunktionen :
array_push
O (∑ var_i, für alle i)array_pop
O (1)array_shift
O (n) - Es müssen alle Schlüssel neu indiziert werdenarray_unshift
O (n + ∑ var_i, für alle i) - es müssen alle Schlüssel neu indiziert werdenArray-Schnittpunkt, Union, Subtraktion :
array_intersect_key
Wenn der Schnittpunkt 100% O (Max (param_i_size) * ∑param_i_count für alle i), wenn der Schnittpunkt 0% O schneidet (∑param_i_size für alle i)array_intersect
wenn der Schnittpunkt 100% O (n ^ 2 * ∑param_i_count, für alle i), wenn der Schnittpunkt 0% O schneidet (n ^ 2)array_intersect_assoc
Wenn der Schnittpunkt 100% O (Max (param_i_size) * ∑param_i_count für alle i), wenn der Schnittpunkt 0% O schneidet (∑param_i_size für alle i)array_diff
O (π param_i_size, für alle i) - Das ist das Produkt aller param_sizesarray_diff_key
O (∑ param_i_size, für i! = 1) - Dies liegt daran, dass wir nicht über das erste Array iterieren müssen.array_merge
O (∑ array_i, i! = 1) - muss nicht über das erste Array iterieren+
(union) O (n), wobei n die Größe des 2. Arrays ist (dh array_first + array_second) - weniger Overhead als array_merge, da es nicht neu nummeriert werden mussarray_replace
O (∑ array_i, für alle i)Zufall :
shuffle
Auf)array_rand
O (n) - Erfordert eine lineare Abfrage.Offensichtlicher Big-O :
array_fill
Auf)array_fill_keys
Auf)range
Auf)array_splice
O (Versatz + Länge)array_slice
O (Versatz + Länge) oder O (n), wenn Länge = NULLarray_keys
Auf)array_values
Auf)array_reverse
Auf)array_pad
O (pad_size)array_flip
Auf)array_sum
Auf)array_product
Auf)array_reduce
Auf)array_filter
Auf)array_map
Auf)array_chunk
Auf)array_combine
Auf)Ich möchte Eureqa dafür danken, dass es einfach ist, das Big-O der Funktionen zu finden. Es ist ein erstaunliches kostenloses Programm, das die beste Anpassungsfunktion für beliebige Daten finden kann.
BEARBEITEN:
Für diejenigen, die daran zweifeln, dass es sich um PHP-Array-Lookups handelt
O(N)
, habe ich einen Benchmark geschrieben, um dies zu testen (sie sind immer noch effektivO(1)
für die realistischsten Werte).quelle
Die Erklärung für den Fall, den Sie speziell beschreiben, ist, dass assoziative Arrays als Hash-Tabellen implementiert sind. Die Suche nach Schlüssel (und entsprechend
array_key_exists
) ist also O (1). Arrays werden jedoch nicht nach Wert indiziert. Daher ist die einzige Möglichkeit, im allgemeinen Fall festzustellen, ob ein Wert im Array vorhanden ist, eine lineare Suche. Da gibt es keine Überraschung.Ich glaube nicht, dass es eine spezifische umfassende Dokumentation der algorithmischen Komplexität von PHP-Methoden gibt. Wenn es jedoch groß genug ist, um den Aufwand zu rechtfertigen, können Sie jederzeit den Quellcode durchsehen .
quelle
Sie möchten fast immer
isset
anstelle von verwendenarray_key_exists
. Ich schaue nicht auf die Interna, aber ich bin mir ziemlich sicher, dass diesarray_key_exists
O (N) ist, da es über jeden einzelnen Schlüssel des Arrays iteriert, währendisset
versucht wird, mit demselben Hash-Algorithmus auf das Element zuzugreifen, der beim Zugriff verwendet wird ein Array-Index. Das sollte O (1) sein.Ein "Gotcha", auf das Sie achten sollten, ist Folgendes:
Ich war neugierig und habe den Unterschied gemessen:
is_set:
0,132308959961 Sekundenarray_key_exists:
2,33202195168 SekundenDies zeigt natürlich keine zeitliche Komplexität, aber es zeigt, wie die beiden Funktionen miteinander verglichen werden.
Vergleichen Sie zum Testen der Zeitkomplexität die Zeit, die zum Ausführen einer dieser Funktionen auf der ersten und der letzten Taste benötigt wird.
quelle
$arrray[] = $append
vs.array_push($array, $append)
Zweitens unterscheidet array_key_exists auch zwischen nicht gesetzten und Nullwerten. Denn$a = array('fred' => null);
array_key_exists('fred', $a)
wird true zurückgeben, währendisset($['fred'])
false zurückgegeben wird. Dieser zusätzliche Schritt ist nicht trivial und verlängert die Ausführungszeit erheblich.Wenn Menschen in der Praxis Probleme mit Schlüsselkollisionen haben, implementieren sie Container mit einer sekundären Hash-Suche oder einem ausgeglichenen Baum. Der ausgeglichene Baum würde O (log n) Worst-Case-Verhalten und O (1) Durchschnitt ergeben. Fall (der Hash selbst). Der Overhead lohnt sich in den meisten praktischen Anwendungen in Speicheranwendungen nicht, aber vielleicht gibt es Datenbanken, die diese Form der gemischten Strategie als Standardfall implementieren.
quelle