Po pierwsze, rozumiem, że w 90% aplikacji różnica w wydajności jest całkowicie nieistotna, ale muszę tylko wiedzieć, która konstrukcja jest szybsza. To i ...
Informacje dostępne obecnie w sieci są niejasne. Wiele osób twierdzi, że foreach jest złe, ale technicznie powinno być szybsze, ponieważ ma uprościć pisanie przechodzenia przez tablicę przy użyciu iteratorów. Iteratory, które znowu mają być szybsze, ale w PHP są również pozornie martwo wolne (czy to nie jest rzecz PHP?). Mówię o funkcjach tablicowych: next () prev () reset () itd. Cóż, jeśli są to nawet funkcje, a nie jedna z tych funkcji języka PHP, które wyglądają jak funkcje.
Aby trochę zawęzić : nie interesuje mnie przechodzenie po tablicach w krokach o wartości większej niż 1 (bez kroków ujemnych, tj. Odwrotna iteracja). Nie interesuje mnie również przechodzenie do i z dowolnych punktów, tylko od 0 do długości. Nie widzę również regularnego manipulowania tablicami z więcej niż 1000 kluczy, ale widzę, że tablica jest wielokrotnie przechodzona w logice aplikacji! Również jeśli chodzi o operacje, w dużej mierze tylko manipulacja strunami i echo.
Oto kilka witryn referencyjnych:
http://www.phpbench.com/
http://www.php.lt/benchmark/phpbench.php
Co słyszę wszędzie:
foreach
jest wolny, a zatemfor
/while
jest szybszy- PHP
foreach
kopiuje tablicę, po której iteruje; aby przyspieszyć, musisz użyć referencji - taki kod: jest szybszy niż plik
$key = array_keys($aHash); $size = sizeOf($key);
for ($i=0; $i < $size; $i++)foreach
Oto mój problem. Napisałem ten skrypt testowy: http://pastebin.com/1ZgK07US i bez względu na to, ile razy uruchamiam skrypt, otrzymuję coś takiego:
foreach 1.1438131332397
foreach (using reference) 1.2919359207153
for 1.4262869358063
foreach (hash table) 1.5696921348572
for (hash table) 2.4778981208801
W skrócie:
foreach
jest szybszy niż wforeach
przypadku odniesieniaforeach
jest szybszy niżfor
foreach
jest szybszy niżfor
w przypadku tablicy mieszającej
Czy ktoś może wyjaśnić?
- czy robię coś źle?
- Czy odniesienia do PHP foreach naprawdę robią różnicę? Chodzi mi o to, dlaczego nie miałby go skopiować, jeśli przekazujesz przez odniesienie?
- Jaki jest równoważny kod iteratora dla instrukcji foreach; Widziałem kilka w sieci, ale za każdym razem, gdy je testuję, czas jest daleko; Przetestowałem również kilka prostych konstrukcji iteratorów, ale wydaje mi się, że nigdy nie uzyskałem nawet przyzwoitych wyników - czy iteratory tablic w PHP są po prostu okropne?
- Czy istnieją szybsze sposoby / metody / konstrukcje do iteracji przez tablicę inną niż FOR / FOREACH (i WHILE)?
Wersja PHP 5.3.0
Edycja: odpowiedź Z pomocą ludzi tutaj mogłem zebrać odpowiedzi na wszystkie pytania. Podsumuję je tutaj:
- "Czy robię coś źle?" Wydaje się, że konsensus jest następujący: tak, nie mogę używać echa w testach porównawczych. Osobiście nadal nie rozumiem, dlaczego echo jest jakąś funkcją z losowym czasem wykonania lub jak każda inna funkcja jest w jakiś sposób inna - to i zdolność tego skryptu do generowania dokładnie tych samych wyników dla każdego, lepiej niż wszystko, jest trudna wyjaśnić, chociaż po prostu „używasz echa” (cóż, czego powinienem był użyć). Jednak przyznaję, że test powinien być wykonany z czymś lepszym; chociaż nie przychodzi na myśl idealny kompromis.
- „Czy odniesienia do PHP foreach naprawdę robią różnicę? Mam na myśli, dlaczego nie miałoby ich kopiować, jeśli przekazujesz je przez referencję? ircmaxell pokazuje, że tak, dalsze testy wydają się dowodzić, że w większości przypadków odniesienia powinny być szybsze - chociaż biorąc pod uwagę mój powyższy fragment kodu, zdecydowanie nie oznacza to wszystkiego. Akceptuję, że problem jest prawdopodobnie zbyt nieintuicyjny, aby zawracać sobie głowę na takim poziomie i wymagałby czegoś ekstremalnego, takiego jak dekompilacja, aby faktycznie określić, która jest lepsza dla każdej sytuacji.
- „Jaki jest odpowiednik kodu iteratora dla instrukcji foreach? Widziałem kilka w sieci, ale za każdym razem, gdy je testuję, czas jest daleki; przetestowałem również kilka prostych konstrukcji iteratora, ale nigdy nie uzyskałem nawet przyzwoitych wyników - czy iteratory tablic w PHP są po prostu okropne? " ircmaxell udzielił odpowiedzi poniżej; chociaż kod może być ważny tylko dla wersji PHP> = 5
- „Czy istnieją szybsze sposoby / metody / konstrukcje do iteracji przez tablicę inną niż FOR / FOREACH (i WHILE)?” Dziękuję Gordonowi za odpowiedź. Używanie nowych typów danych w PHP5 powinno zapewnić albo wzrost wydajności, albo zwiększenie pamięci (z których każdy może być pożądany w zależności od sytuacji). Chociaż pod względem szybkości wiele nowych typów tablic nie wydaje się być lepszych niż array (), splpriorityqueue i splobjectstorage wydają się być znacznie szybsze. Link udostępniony przez Gordona: http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/
Dziękuję wszystkim, którzy próbowali pomóc.
Prawdopodobnie będę trzymać się foreach (wersji bez odniesienia) dla każdego prostego przejścia.
Odpowiedzi:
Osobiście uważam, że wykorzystam to, co ma sens w kontekście. Osobiście prawie nigdy nie używam
for
do przechodzenia przez tablicę. Używam go do innych typów iteracji, aleforeach
jest po prostu zbyt łatwy ... W większości przypadków różnica czasu będzie minimalna.Najważniejsze, na co należy zwrócić uwagę, to:
for ($i = 0; $i < count($array); $i++) {
To droga pętla, ponieważ wywołuje licznik w każdej pojedynczej iteracji. Dopóki tego nie robisz, nie sądzę, że to naprawdę ma znaczenie ...
Jeśli chodzi o odniesienie, które robi różnicę, PHP używa kopiowania przy zapisie, więc jeśli nie piszesz do tablicy, będzie stosunkowo niewielki narzut podczas wykonywania pętli. Jeśli jednak zaczniesz modyfikować tablicę w tablicy, zaczniesz widzieć różnice między nimi (ponieważ trzeba będzie skopiować całą tablicę, a odniesienie może po prostu zmodyfikować w tekście) ...
Jeśli chodzi o iteratory,
foreach
jest równoważne:$it->rewind(); while ($it->valid()) { $key = $it->key(); // If using the $key => $value syntax $value = $it->current(); // Contents of loop in here $it->next(); }
O ile istnieją szybsze sposoby iteracji, tak naprawdę zależy to od problemu. Ale naprawdę muszę zapytać, dlaczego? Rozumiem, że chcesz zwiększyć wydajność, ale myślę, że tracisz czas na mikro-optymalizację. Pamiętaj,
Premature Optimization Is The Root Of All Evil
...Edycja: na podstawie komentarza zdecydowałem się przeprowadzić szybki test porównawczy ...
$a = array(); for ($i = 0; $i < 10000; $i++) { $a[] = $i; } $start = microtime(true); foreach ($a as $k => $v) { $a[$k] = $v + 1; } echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => &$v) { $v = $v + 1; } echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => $v) {} echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => &$v) {} echo "Completed in ", microtime(true) - $start, " Seconds\n";
A wyniki:
Completed in 0.0073502063751221 Seconds Completed in 0.0019769668579102 Seconds Completed in 0.0011849403381348 Seconds Completed in 0.00111985206604 Seconds
Więc jeśli modyfikujesz tablicę w pętli, użycie referencji jest kilka razy szybsze ...
A narzut dla samego odniesienia jest w rzeczywistości mniejszy niż kopiowanie tablicy (to jest w 5.3.2) ... Więc wydaje się (przynajmniej w 5.3.2), jakby referencje były znacznie szybsze ...
źródło
"the better standard way to adopt."
Wydajność @srcspider nie jest jedynym kryterium wyboru tego, co przyjąć. zwłaszcza w tak naciąganym przypadku. Szczerze mówiąc, marnujesz tylko swój czasNie jestem pewien, czy to takie zaskakujące. Większość ludzi, którzy kodują w PHP, nie jest dobrze zorientowana w tym, co PHP robi w rzeczywistości. Powiem kilka rzeczy, które będą prawdziwe przez większość czasu:
Jeśli nie modyfikujesz zmiennej, wartość według wartości jest szybsza w PHP. Dzieje się tak, ponieważ i tak jest to liczone odniesienie, a przez wartość daje mniej do zrobienia. Wie, że w momencie, gdy modyfikujesz ten ZVAL (wewnętrzna struktura danych PHP dla większości typów), będzie musiał go zerwać w prosty sposób (skopiuj i zapomnij o innym ZVAL). Ale nigdy go nie modyfikujesz, więc to nie ma znaczenia. Odnośniki sprawiają, że jest to bardziej skomplikowane przy większej księgowości, którą trzeba zrobić, aby wiedzieć, co zrobić, gdy zmienisz zmienną. Więc jeśli jesteś tylko do odczytu, paradoksalnie lepiej nie podkreślać tego za pomocą &. Wiem, to sprzeczne z intuicją, ale to też prawda.
Foreach nie jest powolne. A dla prostej iteracji warunek, dla którego testuje - „czy jestem na końcu tej tablicy” - jest wykonywany przy użyciu kodu natywnego, a nie instrukcji PHP. Nawet jeśli są to opkody buforowane przez APC, nadal jest wolniejsze niż kilka natywnych operacji wykonywanych na gołym metalu.
Używanie pętli for "for ($ i = 0; $ i <count ($ x); $ i ++) jest powolne z powodu funkcji count () i braku zdolności PHP (lub właściwie dowolnego języka interpretowanego) do oceny podczas parsowania czy coś modyfikuje tablicę. Zapobiega to jednokrotnemu oszacowaniu liczby.
Ale nawet jeśli naprawisz to za pomocą "$ c = count ($ x); for ($ i = 0; $ i <$ c; $ i ++) $ i <$ c jest w najlepszym razie zbiorem rozkazów Zend, tak jak jest $ i ++. W trakcie 100000 iteracji może to mieć znaczenie. Na poziomie natywnym każdy wie, co robić. Żadne rozkazy PHP nie są potrzebne do przetestowania warunku „czy jestem na końcu tej tablicy”.
A co ze starą szkołą "while (list (" stuff? Cóż, użycie each (), current () itp.) Będzie wymagało co najmniej jednego wywołania funkcji, które nie jest wolne, ale nie darmowe. Tak, te to znowu kody operacyjne PHP! Więc chociaż + lista + każdy ma również swoje koszty.
Z tych powodów każdy jest, co zrozumiałe, najlepszą opcją dla prostej iteracji.
I nie zapominaj, że jest również najłatwiejszy do odczytania, więc jest korzystny dla wszystkich.
źródło
Jedną rzeczą, na którą należy zwrócić uwagę w testach porównawczych (zwłaszcza phpbench.com), jest to, że chociaż liczby są prawidłowe, testy nie. Wiele testów na phpbench.com robi rzeczy, które są trywialne i nadużywają zdolności PHP do buforowania wyszukiwań tablic w celu wypaczenia testów porównawczych lub w przypadku iteracji po tablicy nie testuje tego w rzeczywistych przypadkach (nikt nie pisze pustego pętle). Zrobiłem własne testy porównawcze, które, jak stwierdziłem, dość odzwierciedlają rzeczywiste wyniki i zawsze pokazują, że natywna iteracyjna składnia języka
foreach
wychodzi na wierzch (niespodzianka, niespodzianka).//make a nicely random array $aHash1 = range( 0, 999999 ); $aHash2 = range( 0, 999999 ); shuffle( $aHash1 ); shuffle( $aHash2 ); $aHash = array_combine( $aHash1, $aHash2 ); $start1 = microtime(true); foreach($aHash as $key=>$val) $aHash[$key]++; $end1 = microtime(true); $start2 = microtime(true); while(list($key) = each($aHash)) $aHash[$key]++; $end2 = microtime(true); $start3 = microtime(true); $key = array_keys($aHash); $size = sizeOf($key); for ($i=0; $i<$size; $i++) $aHash[$key[$i]]++; $end3 = microtime(true); $start4 = microtime(true); foreach($aHash as &$val) $val++; $end4 = microtime(true); echo "foreach ".($end1 - $start1)."\n"; //foreach 0.947947025299 echo "while ".($end2 - $start2)."\n"; //while 0.847212076187 echo "for ".($end3 - $start3)."\n"; //for 0.439476966858 echo "foreach ref ".($end4 - $start4)."\n"; //foreach ref 0.0886030197144 //For these tests we MUST do an array lookup, //since that is normally the *point* of iteration //i'm also calling noop on it so that PHP doesn't //optimize out the loopup. function noop( $value ) {} //Create an array of increasing indexes, w/ random values $bHash = range( 0, 999999 ); shuffle( $bHash ); $bstart1 = microtime(true); for($i = 0; $i < 1000000; ++$i) noop( $bHash[$i] ); $bend1 = microtime(true); $bstart2 = microtime(true); $i = 0; while($i < 1000000) { noop( $bHash[$i] ); ++$i; } $bend2 = microtime(true); $bstart3 = microtime(true); foreach( $bHash as $value ) { noop( $value ); } $bend3 = microtime(true); echo "for ".($bend1 - $bstart1)."\n"; //for 0.397135972977 echo "while ".($bend2 - $bstart2)."\n"; //while 0.364789962769 echo "foreach ".($bend3 - $bstart3)."\n"; //foreach 0.346374034882
źródło
Jest rok 2020 i wiele rzeczy ewoluowało dzięki php 7.4 i opcache .
Oto test porównawczy OP ^, uruchomiony jako unix CLI , bez części echo i html.
Test został uruchomiony lokalnie na zwykłym komputerze.
php -v PHP 7.4.6 (cli) (built: May 14 2020 10:02:44) ( NTS )
Zmodyfikowany skrypt testowy:
<?php ## preperations; just a simple environment state $test_iterations = 100; $test_arr_size = 1000; // a shared function that makes use of the loop; this should // ensure no funny business is happening to fool the test function test($input) { //echo '<!-- '.trim($input).' -->'; } // for each test we create a array this should avoid any of the // arrays internal representation or optimizations from getting // in the way. // normal array $test_arr1 = array(); $test_arr2 = array(); $test_arr3 = array(); // hash tables $test_arr4 = array(); $test_arr5 = array(); for ($i = 0; $i < $test_arr_size; ++$i) { mt_srand(); $hash = md5(mt_rand()); $key = substr($hash, 0, 5).$i; $test_arr1[$i] = $test_arr2[$i] = $test_arr3[$i] = $test_arr4[$key] = $test_arr5[$key] = $hash; } ## foreach $start = microtime(true); for ($j = 0; $j < $test_iterations; ++$j) { foreach ($test_arr1 as $k => $v) { test($v); } } echo 'foreach '.(microtime(true) - $start)."\n"; ## foreach (using reference) $start = microtime(true); for ($j = 0; $j < $test_iterations; ++$j) { foreach ($test_arr2 as &$value) { test($value); } } echo 'foreach (using reference) '.(microtime(true) - $start)."\n"; ## for $start = microtime(true); for ($j = 0; $j < $test_iterations; ++$j) { $size = count($test_arr3); for ($i = 0; $i < $size; ++$i) { test($test_arr3[$i]); } } echo 'for '.(microtime(true) - $start)."\n"; ## foreach (hash table) $start = microtime(true); for ($j = 0; $j < $test_iterations; ++$j) { foreach ($test_arr4 as $k => $v) { test($v); } } echo 'foreach (hash table) '.(microtime(true) - $start)."\n"; ## for (hash table) $start = microtime(true); for ($j = 0; $j < $test_iterations; ++$j) { $keys = array_keys($test_arr5); $size = sizeOf($test_arr5); for ($i = 0; $i < $size; ++$i) { test($test_arr5[$keys[$i]]); } } echo 'for (hash table) '.(microtime(true) - $start)."\n";
Wynik:
foreach 0.0032877922058105 foreach (using reference) 0.0029420852661133 for 0.0025191307067871 foreach (hash table) 0.0035080909729004 for (hash table) 0.0061779022216797
Jak widać, ewolucja jest szalona, około 560 razy szybciej niż odnotowano w 2012 roku.
Na moich maszynach i serwerach, po licznych eksperymentach, podstawy pętli są najszybsze. Jest to jeszcze jaśniejsze w przypadku zagnieżdżonych pętli ( $ i $ j $ k ..)
Jest również najbardziej elastyczny w użyciu i moim zdaniem ma lepszą czytelność.
źródło
Myślę, ale nie jestem pewien:
for
pętla przyjmuje dwie operacje do sprawdzania i zwiększania wartości.foreach
ładuje dane do pamięci, a następnie iteruje wszystkie wartości.źródło