Was ist schneller: in_array oder isset? [geschlossen]

96

Diese Frage ist nur für mich, da ich immer gerne optimierten Code schreibe, der auch auf billigen langsamen Servern (oder Servern mit viel Verkehr) ausgeführt werden kann.

Ich sah mich um und konnte keine Antwort finden. Ich habe mich gefragt, was zwischen diesen beiden Beispielen schneller ist, wenn ich bedenke, dass die Schlüssel des Arrays in meinem Fall nicht wichtig sind (Pseudocode natürlich):

<?php
$a = array();
while($new_val = 'get over 100k email addresses already lowercased'){
    if(!in_array($new_val, $a){
        $a[] = $new_val;
        //do other stuff
    }
}
?>

<?php
$a = array();
while($new_val = 'get over 100k email addresses already lowercased'){
    if(!isset($a[$new_val]){
        $a[$new_val] = true;
        //do other stuff
    }
}
?>

Da der Punkt der Frage nicht die Array-Kollision ist, möchte ich hinzufügen, dass $a[$new_value]Sie verwenden können , wenn Sie Angst vor kollidierenden Inserts haben $a[md5($new_value)]. Es kann immer noch Kollisionen verursachen, würde jedoch einen möglichen DoS-Angriff verhindern, wenn aus einer vom Benutzer bereitgestellten Datei gelesen wird ( http://nikic.github.com/2011/12/28/Supercolliding-a-PHP-array.html ).

Fabrizio
quelle
3
Wenn Sie immer danach streben, optimierten Code zu schreiben, verwenden Sie dann sicher ab und zu einen Profiler?
Mario
58
Ich stimme für die Wiedereröffnung. Die Frage ist gut formuliert und die Antworten werden mit Fakten und Referenzen unterstützt. Während ein Mikro -optimierung, sind diese Arten von Fragen konstruktiv .
Jason McCreary
5
@JasonMcCreary Sekunde; nur noch eins mehr.
Ja͢ck
6
Dies ist viele Jahre später, aber ich würde dies nicht einmal als Mikrooptimierung betrachten. Bei großen Datenmengen kann dies einen großen Unterschied machen !!
Robert
1
... diese Frage erscheint mir "konstruktiv". Ich werde eine weitere Wiedereröffnungskampagne starten.
Mickmackusa

Antworten:

117

Die bisherigen Antworten sind genau richtig. Die Verwendung ist issetin diesem Fall schneller, weil

  • Es verwendet eine O (1) -Hash-Suche für den Schlüssel, während in_arrayjeder Wert überprüft werden muss, bis eine Übereinstimmung gefunden wird.
  • Als Opcode hat er weniger Overhead als das Aufrufen der in_arrayintegrierten Funktion.

Diese können mithilfe eines Arrays mit Werten (10.000 im folgenden Test) demonstriert werden, wodurch in_arraymehr Suchen erforderlich wird.

isset:    0.009623
in_array: 1.738441

Dies baut auf Jasons Benchmark auf, indem einige zufällige Werte eingegeben und gelegentlich ein Wert gefunden wird, der im Array vorhanden ist. Alles zufällig, also Vorsicht, dass die Zeiten schwanken.

$a = array();
for ($i = 0; $i < 10000; ++$i) {
    $v = rand(1, 1000000);
    $a[$v] = $v;
}
echo "Size: ", count($a), PHP_EOL;

$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    isset($a[rand(1, 1000000)]);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    in_array(rand(1, 1000000), $a);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;
David Harkness
quelle
Ich kenne mich mit Hashes aus, frage mich aber, warum etwas Ähnliches nicht für Arrays-Werte getan wird, wenn dies möglich ist, um Funktionen zu beschleunigen. Es reduziert auch den Speicherverbrauch, wenn ähnliche Werte verwendet werden, indem einfach ein zusätzliches Hashing für den Wert hinzugefügt wird. Richtig?
Fabrizio
3
@Fabrizio - Array-Werte können dupliziert werden und enthalten nicht hashbare Objekte. Schlüssel müssen eindeutig sein und können nur Zeichenfolgen und Ganzzahlen sein, wodurch sie leicht hashbar sind. Sie könnten zwar eine Eins-zu-Eins-Zuordnung erstellen, die sowohl Schlüssel als auch Werte hasht, aber so funktioniert das PHP-Array nicht.
David Harkness
3
Wenn Sie sicher sind, dass Ihr Array eindeutige Werte enthält, gibt es eine andere Option - flip + isset .
Arkadij Kuzhel
Erwähnenswert ist, dass ein gespiegeltes Isset in diesem Beispiel immer noch schneller ist als in_array: `` `$ start = microtime (true); $ foo = array_flip ($ a); für ($ i = 0; $ i <10000; ++ $ i) {isset ($ foo [rand (1, 1000000)]); } $ total_time = microtime (true) - $ start; echo "Gesamtzeit (gespiegeltes Isset):", number_format ($ total_time, 6), PHP_EOL;
Andre Baumeier
@AndreBaumeier Was schneller ist, hängt von der Größe des Arrays und der Anzahl der durchgeführten Tests ab. Das Umdrehen eines zehntausend-Elemente-Arrays zur Durchführung von drei Tests ist wahrscheinlich nicht effizient.
David Harkness
42

Welches ist schneller: isset()vs.in_array()

isset() ist schneller.

Während es offensichtlich sein sollte, isset()testet nur ein einzelner Wert. Während in_array()wird über das gesamte Array iteriert und der Wert jedes Elements getestet.

Grobes Benchmarking ist recht einfach microtime().

Ergebnisse:

Total time isset():    0.002857
Total time in_array(): 0.017103

Hinweis: Die Ergebnisse waren ähnlich, unabhängig davon, ob sie vorhanden waren oder nicht.

Code:

<?php
$a = array();
$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    isset($a['key']);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    in_array('key', $a);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

exit;

Zusätzliche Ressourcen

Ich möchte Sie ermutigen, sich auch Folgendes anzuschauen:

Jason McCreary
quelle
Schöne Lösung. Ich bin überrascht, dass mehr Leute ihre Funktionen / ihren Code nicht mehr mit microtime()anderen Tools teilen . Unglaublich wertvoll.
Nickhar
1
Durch das Durchsuchen eines leeren Arrays nach demselben Schlüssel wird nur der Aufwand für den Aufruf der in_arrayFunktion im Vergleich zur Verwendung des issetintegrierten Schlüssels hervorgehoben . Dies ist besser mit einem Array, das eine Reihe zufälliger Schlüssel enthält und gelegentlich nach einem vorhandenen Schlüssel / Wert sucht.
David Harkness
Ich benutze viel Benchmarks und Mikrozeit, aber ich habe auch festgestellt, dass ich beim Testen whileund foreachbei jeder Aktualisierung unterschiedliche "Gewinner" bekam. Es hängt immer von zu vielen Servervariablen ab. Das Beste ist, eine sehr große Anzahl von Malen zu unterschiedlichen Zeiten zu durchlaufen und diejenige zu erhalten, die häufiger gewinnt, oder einfach zu wissen, was im Hintergrund passiert, und zu wissen, dass es der endgültige Gewinner sein wird egal was
Fabrizio
@ David Harkness, du hast meine Antwort schon nicht ausgewählt. Wenn du mehr willst, stell dich auf meine Schultern und poste deine eigene Antwort. :) Wenn der Funktionsaufwand im Vergleich zu bereits erheblich höher isset()ist, warum denken Sie dann , wenn Sie ihn an ein größeres Array übergeben, wird er schneller ?
Jason McCreary
1
@Fabrizio - Informieren Sie sich über Hashing-Funktionen und Hash-Tabellen .
David Harkness
19

Die Verwendung isset()nutzt die schnellere Suche, da eine Hash-Tabelle verwendet wird , sodass keine O(n)Suche erforderlich ist .

Der Schlüssel wird zuerst mit der djb-Hash-Funktion gehasht, um den Bucket mit ähnlich gehashten Schlüsseln zu bestimmen O(1). Der Bucket wird dann iterativ durchsucht, bis der genaue Schlüssel in gefunden ist O(n).

Abgesehen von absichtlichen Hash-Kollisionen bietet dieser Ansatz eine viel bessere Leistung als in_array().

Beachten Sie, dass bei der Verwendung isset()in der von Ihnen gezeigten Weise zum Übergeben der endgültigen Werte an eine andere Funktion die array_keys()Erstellung eines neuen Arrays erforderlich ist . Ein Speicherkompromiss kann erzielt werden, indem die Daten sowohl in den Schlüsseln als auch in den Werten gespeichert werden.

Aktualisieren

Um zu sehen, wie sich Ihre Entscheidungen zum Code-Design auf die Laufzeitleistung auswirken, können Sie die kompilierte Version Ihres Skripts überprüfen :

echo isset($arr[123])

compiled vars:  !0 = $arr
line     # *  op                           fetch      ext  return  operands
-----------------------------------------------------------------------------
   1     0  >   ZEND_ISSET_ISEMPTY_DIM_OBJ              2000000  ~0      !0, 123
         1      ECHO                                                 ~0
         2    > RETURN                                               null

echo in_array(123, $arr)

compiled vars:  !0 = $arr
line     # *  op                           fetch      ext  return  operands
-----------------------------------------------------------------------------
   1     0  >   SEND_VAL                                             123
         1      SEND_VAR                                             !0
         2      DO_FCALL                                 2  $0      'in_array'
         3      ECHO                                                 $0
         4    > RETURN                                               null

Es wird nicht nur in_array()eine relativ ineffiziente O(n)Suche verwendet, sondern es muss auch eine Funktion ( DO_FCALL) aufgerufen werden, während hierfür isset()ein einzelner Opcode ( ZEND_ISSET_ISEMPTY_DIM_OBJ) verwendet wird.

Jack
quelle
7

Der zweite ist schneller, da nur nach diesem bestimmten Array-Schlüssel gesucht wird und nicht über das gesamte Array iteriert werden muss, bis es gefunden wird (jedes Array-Element wird angezeigt, wenn es nicht gefunden wird).

Mike Brant
quelle
hängt aber auch vom Aufenthaltsort einer gesuchten Var im globalen Bereich ab
el Dude
@ EL2002, können Sie diese Aussage bitte näher erläutern?
Fabrizio
1
Mike, würdest isset()du nicht das ganze Array betrachten, auch wenn es nicht gefunden wird?
Fabrizio
1
@Fabrizio Nein, es muss nicht iteriert werden. Intern (in C) ist das PHP-Array nur eine Hash-Tabelle. Um einen einzelnen Indexwert nachzuschlagen, erstellt C nur einen Hash dieses Werts und sucht nach dem zugewiesenen Speicherort im Speicher. Es gibt dort entweder einen Wert oder keinen.
Mike Brant
1
@Fabrizio Dieser Artikel bietet einen guten Überblick darüber, wie Arrays in C von PHP intern dargestellt werden. nikic.github.com/2012/03/28/…
Mike Brant