Nachdem ich PHP nun eine Weile verwendet habe, habe ich festgestellt, dass nicht alle PHP Funktionen so schnell wie erwartet eingebaut sind. Berücksichtigen Sie die beiden folgenden möglichen Implementierungen einer Funktion, die mithilfe eines zwischengespeicherten Primer-Arrays ermittelt, ob eine Zahl Primzahlen 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 ist darauf zurückzuführen, dass in_array mit einer linearen Suche O(n) implementiert wird, die sich mit zunehmendem $prime_array
linear verlangsamt. Die array_key_exists
-Funktion wird mit einer Hash-Suche O(1) implementiert, die sich nur dann verlangsamt, wenn die Hash-Tabelle extrem gefüllt wird (in diesem Fall ist dies nur O (n)).
Bisher musste ich die Big-O's durch Ausprobieren und gelegentlich herausfinden, und gelegentlich Blick auf den Quellcode . Nun zur Frage ...
Gibt es eine Liste der theoretischen (oder praktischen) großen O-Zeiten für alle PHP integrierten Funktionen?
* oder zumindest die interessanten
Zum Beispiel ist es sehr schwer vorherzusagen, was das große O der Funktionen ist, da die mögliche Implementierung von unbekannten Kerndatenstrukturen von PHP abhängt: array_merge, array_merge_recursive, array_reverse, array_intersect, array_combine, str_replace (mit Array-Eingaben) usw.
Da es nicht so aussieht, als hätte jemand das getan, bevor ich dachte, es wäre eine gute Idee, es irgendwo als Referenz zu haben. Ich bin aber entweder über Benchmark oder Code-Skimming gegangen, um die array_*
-Funktionen zu charakterisieren. Ich habe versucht, das interessantere Big-O an die Spitze zu bringen. Diese Liste ist nicht vollständig.
Hinweis: Alle Big-O, bei denen unter der Annahme einer Hash-Suche berechnet wird, lautet O(1), obwohl es wirklich O (n) ist. Der Koeffizient von n ist so niedrig, dass der RAM-Aufwand für das Speichern eines ausreichend großen Arrays Sie verletzen würde, bevor die Eigenschaften des Nachschlages von Big-O wirksam werden. Zum Beispiel ist die Differenz zwischen einem Aufruf von array_key_exists
bei N = 1 und N = 1.000.000 um 50% Zeitsteigerung.
Interessante Punkte:
isset
array_key_exists
ist viel schneller als in_array
und array_search
+
(union) ist etwas schneller als array_merge
(und sieht schöner aus). Aber es funktioniert anders, also vergiss das nicht.shuffle
befindet sich auf derselben Big-O-Schicht wie array_Rand
array_pop
/array_Push
ist schneller als array_shift
/array_unshift
, weil der Index erneut bestraft wird_/Lookups:
array_key_exists
O(n), aber sehr nahe an O(1) - dies ist auf lineares Polling in Kollisionen zurückzuführen, aber da die Wahrscheinlichkeit von Kollisionen sehr gering ist, ist der Koeffizient auch sehr klein. Ich finde, Sie behandeln Hash-Lookups wie 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 zwischenspeichert, wenn der Schlüssel hartcodiert ist. Dies führt zu einer Beschleunigung in Fällen, in denen derselbe Schlüssel wiederholt verwendet wird.
in_array
O(n) - Dies geschieht, weil das Array linear durchsucht wird, bis der Wert gefunden wird.
array_search
O(n) - verwendet dieselbe Kernfunktion wie in_array, gibt jedoch den Wert zurück.
Warteschlangenfunktionen:
array_Push
O (∑ var_i, für alle i)
array_pop
O(1)
array_shift
O(n) - Alle Schlüssel müssen neu indiziert werden
array_unshift
O (n + ∑ var_i, für alle i) - Alle Schlüssel müssen neu indiziert werden
Array Schnittpunkt, Vereinigung, Subtraktion:
array_intersect_key
wenn der Schnittpunkt 100% O (Max (param_i_size)) * ∑param_i_count für alle i) ist, wenn der Schnittpunkt 0% O (∑param_i_size, für alle i) schneidet
array_intersect
wenn die Kreuzung 100% für O (n ^ 2 * ∑param_i_count für alle i) ist, wenn die Kreuzung 0% O (n ^ 2) schneidet
array_intersect_assoc
wenn der Schnittpunkt 100% O (Max (param_i_size)) * ∑param_i_count für alle i) ist, wenn der Schnittpunkt 0% O (∑param_i_size, für alle i) schneidet
array_diff
O (π param_i_size, für alle i) - Das ist ein Produkt aller param_sizes
array_diff_key
O (∑ param_i_size, für i! = 1) - dies liegt daran, dass wir das erste Array nicht durchlaufen müssen.
array_merge
O (∑ array_i, i! = 1) - das erste Array muss nicht wiederholt werden
+
(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 muss
array_replace
O (∑ array_i, für alle i)
Zufällig:
shuffle
O (n)
array_Rand
O(n) - Erfordert eine lineare Abfrage.
Offensichtliches Big-O:
array_fill
O (n)
array_fill_keys
O (n)
range
O (n)
array_splice
O (Offset + Länge)
array_slice
O (Offset + Länge) oder O(n), wenn length = 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)
Ich möchte mich bei Eureqa dafür bedanken, dass Sie das Big-O der Funktionen leicht gefunden haben. Es ist ein erstaunliches freies Programm, das die am besten geeignete Funktion für beliebige Daten findet.
BEARBEITEN:
Für diejenigen, die bezweifeln, dass PHP Array-Lookups O(N)
sind, habe ich einen Benchmark geschrieben, um dies zu testen (sie sind immer noch O(1)
für die meisten realistischen Werte).
$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 );
}
Sie möchten fast immer isset
anstelle von array_key_exists
verwenden. Ich schaue nicht auf die Interna, aber ich bin mir ziemlich sicher, dass array_key_exists
O(N) ist, weil es jeden einzelnen Schlüssel des Arrays durchläuft, während isset
versucht, auf das Element mit demselben Hash-Algorithmus zuzugreifen Dies wird verwendet, wenn Sie auf einen Arrayindex zugreifen. Das sollte O (1) sein.
Ein "Gotcha", auf den Sie achten sollten, ist:
$search_array = array('first' => null, 'second' => 4);
// returns false
isset($search_array['first']);
// returns true
array_key_exists('first', $search_array);
Ich war neugierig, also habe ich den Unterschied gemessen:
<?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 Sekundenarray_key_exists:
2.33202195168 Sekunden
Natürlich zeigt dies keine zeitliche Komplexität, aber es zeigt, wie die beiden Funktionen miteinander verglichen werden.
Vergleichen Sie zum Testen der zeitlichen Komplexität die Zeit, die zum Ausführen einer dieser Funktionen für die erste und die letzte Taste erforderlich ist.
Die Erklärung für den Fall, den Sie speziell beschreiben, ist, dass assoziative Arrays als Hashtabellen implementiert werden. Nach Schlüssel suchen (und entsprechend array_key_exists
) lautet O (1). Arrays werden jedoch nicht nach Werten indiziert. Daher kann im allgemeinen Fall nur durch eine lineare Suche ermittelt werden, ob ein Wert im Array vorhanden ist. Da gibt es keine Überraschung.
Ich glaube nicht, dass es eine umfassende umfassende Dokumentation der algorithmischen Komplexität von PHP -Methoden gibt. Wenn dies jedoch ein Anliegen ist, das den Aufwand rechtfertigt, können Sie immer durch den Quellcode blättern.
Wenn Menschen in der Praxis mit Schlüsselkollisionen in Schwierigkeiten geraten, würden sie Container mit einer sekundären Hash-Suche oder einem ausgeglichenen Baum implementieren. Der ausgeglichene Baum würde O (log n) Verhalten im schlimmsten Fall ergeben und O(1) avg. case (der hash selbst). Der Overhead lohnt sich bei den meisten praktischen Anwendungen in Speicheranwendungen nicht, aber möglicherweise gibt es Datenbanken, die diese Form gemischter Strategie als Standardfall implementieren.