PHP: Schnellster Weg, um mit undefinierten Array-Schlüsseln umzugehen
Lesezeit: 9 Minuten
Zsolt Szilagyi
In einer sehr engen Schleife muss ich auf Zehntausende von Werten in einem Array zugreifen, das Millionen von Elementen enthält. Der Schlüssel kann undefiniert sein: In diesem Fall darf NULL ohne Fehlermeldung zurückgegeben werden:
Array-Schlüssel vorhanden: Rückgabewert des Elements. Array-Schlüssel existiert nicht: Null zurückgeben.
Ich kenne mehrere Lösungen:
if (isset($lookup_table[$key])) {
return $lookup_table[$key];
} else {
return;
}
Der erste erfordert 2 Lookups im B-TREE: Einen, um das Vorhandensein zu prüfen, einen anderen, um den Wert abzurufen. Das verdoppelt effektiv die Laufzeit.
Der zweite verwendet den Fehlerunterdrückungsoperator und erzeugt somit einen massiven Overhead auf dieser Leitung.
Der dritte ruft den Fehlerhandler auf (der die Einstellung error_reporting überprüft und dann nichts anzeigt) und erzeugt dadurch einen Overhead.
Meine Frage ist, wenn ich eine Möglichkeit verpasse, die Fehlerbehandlung zu vermeiden und dennoch mit einer einzigen Btree-Suche zu arbeiten?
Um einige Fragen zu beantworten:
Das Array speichert die Ergebnisse einer komplexen Berechnung – zu komplex, um in Echtzeit ausgeführt zu werden. Von Milliarden möglicher Werte liefern nur Millionen ein gültiges Ergebnis. Das Array sieht aus wie 1234567 => 23457, 1234999 => 74361, …. Das wird in einer mehrere Megabyte großen PHP-Datei gespeichert und zu Beginn der Ausführung mit include_once-d versehen. Die anfängliche Ladezeit spielt keine Rolle. Wenn der Schlüssel nicht gefunden wird, bedeutet dies einfach, dass dieser bestimmte Wert kein gültiges Ergebnis zurückgibt. Das Problem besteht darin, dies mit 50.000+ pro Sekunde zu erledigen.
Fazit
Da es keine Möglichkeit gibt, den Wert mit einer einzigen Suche und ohne Fehlerbehandlung zu erhalten, habe ich Probleme, eine einzelne Antwort zu akzeptieren. Stattdessen habe ich alle großartigen Beiträge positiv bewertet.
Die wertvollsten Inputs wo:
Verwenden Sie array_key_exists, da es schneller ist als Alternativen
Schauen Sie sich QuickHash von PHP an
Es gab viel Verwirrung darüber, wie PHP mit Arrays umgeht. Wenn Sie den Quellcode überprüfen, werden Sie feststellen, dass alle Arrays ausgeglichene Bäume sind. Das Erstellen eigener Suchmethoden ist in C und C++ üblich, aber in höheren Skriptsprachen wie PHP nicht performant.
isset($lookup_table[$key]) wird zurückkehren false wenn $lookup_table[$key] ist null … ist das nicht das Gegenteil von dem, was Sie wollen? Ich bin mir nicht sicher, wie seine Leistung ist, aber array_key_exists wird zurückkehren true wenn der Schlüssel existiert, aber sein Wert ist null.
– Benutzer428517
21. Mai 2013 um 17:16 Uhr
@sgroves: Das Beispiel hat meine Absicht widergespiegelt. Ich habe die Frage aktualisiert, um sie zu verdeutlichen: Wenn der Schlüssel vorhanden ist, geben Sie den Wert zurück, andernfalls geben Sie null zurück. (Der Wert vorhandener Schlüssel wird niemals null sein.) array_key_exist ist ehrlich gesagt langsamer als die Isset-Version, also habe ich es in meinen Beispielen übersprungen.
“ein Array mit Millionen von Elementen”. Möglicherweise müssen Sie Ihre Strategie überdenken und eine Art Paginierung oder eine Möglichkeit zur Segmentierung der Daten einbeziehen.
– Mike Purcell
21. Mai 2013 um 17:27 Uhr
Mike, Mathew, ich habe meine Frage aktualisiert, um deine zu beantworten. Sean, das ist keine Glaubenssache. Natürlich ist natives C schneller als PHP. Ich bin einfach nicht bereit, einen Monat damit zu verbringen, Code zu schreiben, den ich über Nacht in PHP schreiben kann. 🙂
Zunächst einmal werden Arrays nicht als B-Baum implementiert, sondern als Hash-Tabelle. ein Array von Buckets (indiziert über eine Hash-Funktion), jeder mit einer verknüpften Liste von tatsächlichen Werten (im Falle von Hash-Kollisionen). Das bedeutet, dass Lookup-Zeiten davon abhängen, wie gut die Hash-Funktion die Werte über die Buckets „verteilt“ hat, dh die Anzahl der Hash-Kollisionen ist ein wichtiger Faktor.
Technisch gesehen ist diese Aussage die richtigste:
Abgesehen davon, dass es nicht den Nebeneffekt von Referenzen hat, ist es auch schneller in der Laufzeit, selbst wenn die Suche zweimal durchgeführt wird.
Sie könnten Ihre Arrays in kleinere Teile unterteilen, um lange Suchzeiten zu verringern.
Ich war skeptisch gegenüber diesem Ansatz, also habe ich ein zufälliges Array von etwa 5000 Schlüsseln im Bereich von 0 bis 100.000 generiert und das Array von 0 bis 100.000 durchlaufen lassen. Ich habe Ihre Methode mit Referenz ausprobiert, eine Methode ohne Referenz, aber immer noch eine Hilfsvariable, und einen einfachen Ansatz ohne Hilfsvariable oder Referenz. Ihr Referenzanlauf hat mehr als doppelt so lange gedauert wie die anderen. Der naive Ansatz war geringfügig schneller als der Hilfsvariablen-Ansatz.
– kba
21. Mai 2013 um 17:40 Uhr
@kba Die Antwort erwähnt das modifiziert das Array (wenn der Artikel nicht existiert), also braucht das natürlich Zeit. Es wird auch erwähnt, dass ohne sie die erste von OP gegebene Lösung wahrscheinlich die schnellste ist.
– Jack
21. Mai 2013 um 17:43 Uhr
Auf welche Weise ändert Ihr Ansatz das Array? Ich sehe nicht, dass Sie Änderungen an vornehmen $tmp oder $lookup_table[$key].
– kba
21. Mai 2013 um 17:49 Uhr
@ZsoltSzilagy Wenn Sie eine Referenz auf eine nicht vorhandene Variable erstellen, erstellt PHP die notwendigen Strukturen, um daraus eine gültige Referenz zu machen; in diesem Fall wird ein Array-Eintrag mit Wert erstellt null. Es funktioniert gut, wenn der Artikel natürlich immer da ist 🙂
– Jack
21. Mai 2013 um 17:58 Uhr
Meine Tests deuten jedoch darauf hin, dass dies die Leistung doch nicht verbessert. Selbst nachdem jedes Element im Array einmal durchlaufen wurde, ist die Suchzeit noch schlechter. Meine Tests könnten schlecht sein.
– kba
21. Mai 2013 um 18:09 Uhr
DiverseAndRemote.com
Ich habe ein Benchmarking mit dem folgenden Code durchgeführt:
und ich fand heraus, dass der am schnellsten laufende Test derjenige war, der verwendet isset($array[$key]) ? $array[$key] : null dicht gefolgt von der Lösung, die nur die Fehlerberichterstattung deaktiviert.
Auch bei ausgeschalteter Fehlerberichterstattung werden die Fehler weiterhin generiert.
– Jack
21. Mai 2013 um 17:51 Uhr
Richtig @Jack. Obwohl kontraintuitiv, heißt es error_Berichterstattung aus, nicht error_generation.
– Zsolt Szilagyi
21. Mai 2013 um 17:59 Uhr
Ich frage mich, ob a try/catch und verworfene Ausnahme ist schneller als das Deaktivieren der Fehlerberichterstattung.
– Reaktgular
21. Mai 2013 um 18:51 Uhr
Warnungen werden nicht ausgegeben, so dass ein try/catch in diesem Fall nichts nützt
Nur eine plötzliche Idee, die getestet werden müsste, aber haben Sie versucht, sie zu verwenden array_intersect_key() um die vorhandenen Werte und ein array_merge zu erhalten, um den Rest zu füllen? Es würde die Notwendigkeit einer Schleife für den Zugriff auf die Daten beseitigen. Sowas in der Art :
$searched_keys = array ('key1' => null, 'key2' => null); // the list of the keys to find
$exiting_values = array_intersect_key($lookup_table, $searched_keys);
$all_values = array_merge($searched_keys, $exiting_keys);
Bitte beachten Sie, dass ich es leistungsmäßig nicht ausprobiert habe.
Netter Ansatz. Leider würde $all_values als neues Array betrachtet werden, also würde es einen neuen Index aufbauen.
– Zsolt Szilagyi
21. Mai 2013 um 18:07 Uhr
thomasrutter
Der @-Operator und die error_reporting-Methoden sind beide langsamer als die Verwendung von isset. Bei beiden Methoden wird die Fehlerberichtseinstellung für PHP geändert, aber der Fehlerhandler von PHP wird weiterhin aufgerufen. Der Fehlerhandler prüft die Einstellung error_reporting und beendet sich, ohne etwas zu melden, dies hat jedoch noch einige Zeit in Anspruch genommen.
Netter Ansatz. Leider würde $all_values als neues Array betrachtet werden, also würde es einen neuen Index aufbauen.
– Zsolt Szilagyi
21. Mai 2013 um 18:07 Uhr
Ivan Babic
Ich verwende am liebsten die isset Funktion, anstatt den Fehler zu umgehen. Ich habe eine Funktion erstellt, um zu überprüfen, ob der Schlüssel vorhanden ist, und wenn nicht, wird ein Standardwert zurückgegeben. Bei verschachtelten Arrays müssen Sie nur die anderen Schlüssel der Reihe nach hinzufügen:
Hey, das ist ein netter Ansatz aus der Usability-Perspektive, aber in Bezug auf die Leistung ist es tatsächlich schlechter als die Alternativen. Der Funktionsaufruf erstellt implizit ein Array, der foreach kopiert davon, die erste Zeile dupliziert eine Variable usw. Viel schneller wäre es, $value = isset($array[$key1][$key2]) ? $array[$key1][$key2] : $Standardwert;
– Zsolt Szilagyi
22. Oktober 2019 um 12:30 Uhr
10187300cookie-checkPHP: Schnellster Weg, um mit undefinierten Array-Schlüsseln umzugehenyes
isset($lookup_table[$key])
wird zurückkehrenfalse
wenn$lookup_table[$key]
istnull
… ist das nicht das Gegenteil von dem, was Sie wollen? Ich bin mir nicht sicher, wie seine Leistung ist, aberarray_key_exists
wird zurückkehrentrue
wenn der Schlüssel existiert, aber sein Wert istnull
.– Benutzer428517
21. Mai 2013 um 17:16 Uhr
@sgroves: Das Beispiel hat meine Absicht widergespiegelt. Ich habe die Frage aktualisiert, um sie zu verdeutlichen: Wenn der Schlüssel vorhanden ist, geben Sie den Wert zurück, andernfalls geben Sie null zurück. (Der Wert vorhandener Schlüssel wird niemals null sein.) array_key_exist ist ehrlich gesagt langsamer als die Isset-Version, also habe ich es in meinen Beispielen übersprungen.
– Zsolt Szilagyi
21. Mai 2013 um 17:22 Uhr
Quickhash könnte für deinen Fall gut sein.
– Alex Howansky
21. Mai 2013 um 17:24 Uhr
“ein Array mit Millionen von Elementen”. Möglicherweise müssen Sie Ihre Strategie überdenken und eine Art Paginierung oder eine Möglichkeit zur Segmentierung der Daten einbeziehen.
– Mike Purcell
21. Mai 2013 um 17:27 Uhr
Mike, Mathew, ich habe meine Frage aktualisiert, um deine zu beantworten. Sean, das ist keine Glaubenssache. Natürlich ist natives C schneller als PHP. Ich bin einfach nicht bereit, einen Monat damit zu verbringen, Code zu schreiben, den ich über Nacht in PHP schreiben kann. 🙂
– Zsolt Szilagyi
21. Mai 2013 um 17:53 Uhr