it-swarm.com.de

Liste von Big-O für PHP Funktionen

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.

295
Kendall Hopkins

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:

  1. issetarray_key_exists ist viel schneller als in_array und array_search
  2. + (union) ist etwas schneller als array_merge (und sieht schöner aus). Aber es funktioniert anders, also vergiss das nicht.
  3. shuffle befindet sich auf derselben Big-O-Schicht wie array_Rand
  4. 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).

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

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 Sekunden
array_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.

3
ryeguy

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.

2
Dathan

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.

0
Josh Stern