Kinerja FOR vs FOREACH di PHP

134

Pertama-tama, saya mengerti dalam 90% aplikasi perbedaan kinerja sama sekali tidak relevan, tetapi saya hanya perlu tahu mana yang lebih cepat membangun. Itu dan ...

Informasi yang tersedia saat ini di internet membingungkan. Banyak orang mengatakan foreach itu buruk, tetapi secara teknis seharusnya lebih cepat karena itu menyederhanakan menulis array array menggunakan iterators. Iterator, yang sekali lagi dianggap lebih cepat, tetapi dalam PHP juga tampaknya lambat lambat (atau ini bukan PHP?). Saya berbicara tentang fungsi-fungsi array: next () prev () reset () dll. Baik, jika mereka bahkan berfungsi dan bukan salah satu dari fitur bahasa PHP yang terlihat seperti fungsi.

Untuk mempersempit ini sedikit : Saya tidak menarik melintasi array dalam langkah-langkah lebih dari 1 (tidak ada langkah negatif juga, yaitu. Iterasi terbalik). Saya juga tidak tertarik pada traversal ke dan dari titik arbitrer, hanya 0 panjangnya. Saya juga tidak melihat memanipulasi array dengan lebih dari 1000 tombol yang terjadi secara teratur, tetapi saya memang melihat array yang dilalui beberapa kali dalam logika aplikasi! Juga untuk operasi, sebagian besar hanya manipulasi string dan echo'ing.

Berikut adalah beberapa situs referensi:
http://www.phpbench.com/
http://www.php.lt/benchmark/phpbench.php

Apa yang saya dengar di mana-mana:

  • foreachlambat, dan dengan demikian for/ whilelebih cepat
  • PHP foreachmenyalin array yang diulanginya ; untuk membuatnya lebih cepat, Anda perlu menggunakan referensi
  • kode seperti ini: lebih cepat dari a$key = array_keys($aHash); $size = sizeOf($key);
    for ($i=0; $i < $size; $i++)
    foreach

Inilah masalah saya. Saya menulis skrip pengujian ini: http://pastebin.com/1ZgK07US dan tidak peduli berapa kali saya menjalankan skrip, saya mendapatkan sesuatu seperti ini:

foreach 1.1438131332397
foreach (using reference) 1.2919359207153
for 1.4262869358063
foreach (hash table) 1.5696921348572
for (hash table) 2.4778981208801

Pendeknya:

  • foreachlebih cepat daripada foreachdengan referensi
  • foreach lebih cepat dari for
  • foreachlebih cepat daripada foruntuk tabel hash

Bisakah seseorang menjelaskan?

  1. Apakah saya melakukan sesuatu yang salah?
  2. Apakah referensi PHP foreach benar-benar membuat perbedaan? Maksud saya mengapa tidak menyalinnya jika Anda melewati referensi?
  3. Apa kode iterator yang setara untuk pernyataan foreach; Saya telah melihat beberapa di internet tetapi setiap kali saya mengujinya, waktunya tidak tepat; Saya juga telah menguji beberapa konstruksi iterator sederhana tetapi sepertinya tidak pernah mendapatkan hasil yang layak - apakah array iterators di PHP hanya buruk?
  4. Apakah ada cara / metode / konstruk yang lebih cepat untuk beralih melalui array selain FOR / FOREACH (dan WHILE)?

PHP Versi 5.3.0


Sunting: Jawab Dengan bantuan dari orang-orang di sini saya dapat mengumpulkan jawaban untuk semua pertanyaan. Saya akan meringkasnya di sini:

  1. "Apakah aku melakukan sesuatu yang salah?" Tampaknya konsensus: ya, saya tidak bisa menggunakan gema dalam tolok ukur. Secara pribadi, saya masih tidak melihat bagaimana gema adalah suatu fungsi dengan waktu eksekusi yang acak atau bagaimana fungsi lainnya entah bagaimana berbeda - itu dan kemampuan skrip itu untuk hanya menghasilkan hasil yang sama persis dari foreach lebih baik daripada semuanya sulit untuk menjelaskan meskipun hanya "Anda menggunakan gema" (baik apa yang seharusnya saya gunakan). Namun, saya mengakui tes harus dilakukan dengan sesuatu yang lebih baik; meskipun kompromi yang ideal tidak terlintas dalam pikiran.
  2. "Apakah referensi PHP foreach benar-benar membuat perbedaan? Maksudku mengapa itu tidak akan menyalinnya jika Anda melewati referensi?" ircmaxell menunjukkan bahwa ya, pengujian lebih lanjut tampaknya membuktikan dalam banyak kasus, referensi harus lebih cepat - meskipun dengan potongan kode saya di atas, yang paling pasti tidak berarti semua. Saya menerima masalah ini mungkin terlalu tidak intuitif untuk mengganggu pada tingkat seperti itu dan akan memerlukan sesuatu yang ekstrem seperti mendekompilasi untuk benar-benar menentukan mana yang lebih baik untuk setiap situasi.
  3. "Apa kode iterator yang setara untuk pernyataan foreach; Saya telah melihat beberapa di internet tetapi setiap kali saya menguji mereka waktunya tidak tepat; Saya juga telah menguji beberapa iterator yang sederhana tetapi sepertinya tidak pernah mendapatkan hasil yang layak. - Apakah array iterators di PHP hanya mengerikan? " ircmaxell memberikan jawaban di bawah ini; meskipun kode mungkin hanya valid untuk versi PHP> = 5
  4. "Apakah ada cara / metode / konstruksi yang lebih cepat untuk beralih melalui array selain FOR / FOREACH (dan WHILE)?" Terima kasih kepada Gordon untuk jawabannya. Menggunakan tipe data baru di PHP5 akan memberikan peningkatan kinerja atau peningkatan memori (salah satunya mungkin diinginkan tergantung pada situasi Anda). Meskipun kecepatan banyak jenis baru array tampaknya tidak lebih baik daripada array (), splpriorityqueue dan splobjectstorage tampaknya jauh lebih cepat. Tautan disediakan oleh Gordon: http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/

Terima kasih semuanya yang mencoba membantu.

Saya kemungkinan akan berpegang teguh pada foreach (versi non-referensi) untuk setiap traversal sederhana.

srcspider
sumber
7
Aturan 2.71 tentang tolok ukur: jangan gema ke tolok ukur.
Mchl
1
foreach dengan referensi harus ditandai dengan untuk dengan referensi. Anda memiliki kesimpulan yang salah di sana. setiap penggunaan referensi jelas akan lebih lambat dari itu tanpa referensi, bahkan dalam loop do-while.
bcosca
2
Karena ini untuk php 5.3, Anda mungkin juga ingin mempertimbangkan untuk menguji Jenis Data Spl baru vs Array. Atau lihat saja di sini: matthewturland.com/2010/05/20/new-spl-features-in-php-5-3
Gordon
@ Mchl: Saya menjalankannya beberapa kali, dan mendapatkan hasil yang sama - jika gema merusak tolok ukur maka bukankah seharusnya saya mendapatkan hasil yang benar-benar acak? juga saya ingin mengulangi sesuatu dan mengeluarkannya sehingga gema sebenarnya sangat penting bagi saya; jika foreach lebih cepat ketika echo'ing maka itu adalah sejumlah besar kode di mana saya harus menggunakan foreach. @ stillstanding: apa yang saya dengar pada dasarnya seperti "referensi dalam foreach membuat lebih cepat (selalu), selalu menulis dengan referensi", itu sebabnya saya diuji seperti itu - saya tidak benar-benar tertarik dibandingkan dengan loop referensi lain
srcspider
2
pertanyaan kosong ini seharusnya dilarang. serta situs phpbench yang menipu
Your Common Sense

Jawaban:

110

Pendapat pribadi saya adalah menggunakan apa yang masuk akal dalam konteksnya. Secara pribadi saya hampir tidak pernah menggunakan foruntuk array traversal. Saya menggunakannya untuk jenis iterasi lain, tetapi foreachterlalu mudah ... Perbedaan waktu akan menjadi minimal dalam banyak kasus.

Hal besar yang harus diperhatikan adalah:

for ($i = 0; $i < count($array); $i++) {

Itu perulangan yang mahal, karena ia menyebut mengandalkan pada setiap iterasi tunggal. Selama Anda tidak melakukan itu, saya tidak berpikir itu benar-benar penting ...

Adapun referensi membuat perbedaan, PHP menggunakan copy-on-write, jadi jika Anda tidak menulis ke array, akan ada overhead yang relatif sedikit saat perulangan. Namun, jika Anda mulai memodifikasi array di dalam array, di situlah Anda akan mulai melihat perbedaan di antara mereka (karena orang perlu menyalin seluruh array, dan referensi dapat memodifikasi inline) ...

Adapun iterator, foreachsetara dengan:

$it->rewind();
while ($it->valid()) {
    $key = $it->key();     // If using the $key => $value syntax
    $value = $it->current();

    // Contents of loop in here

    $it->next();
}

Sejauh ada cara yang lebih cepat untuk beralih, itu benar-benar tergantung pada masalahnya. Tetapi saya benar-benar perlu bertanya, mengapa? Saya mengerti ingin membuat segalanya lebih efisien, tetapi saya pikir Anda membuang-buang waktu untuk optimasi mikro. Ingat, Premature Optimization Is The Root Of All Evil...

Sunting: Berdasarkan komentar, saya memutuskan untuk melakukan patokan cepat ...

$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";

Dan hasilnya:

Completed in 0.0073502063751221 Seconds
Completed in 0.0019769668579102 Seconds
Completed in 0.0011849403381348 Seconds
Completed in 0.00111985206604 Seconds

Jadi, jika Anda memodifikasi array dalam loop, beberapa kali lebih cepat untuk menggunakan referensi ...

Dan overhead hanya untuk referensi sebenarnya kurang dari menyalin array (ini pada 5.3.2) ... Jadi muncul (setidaknya 5.3.2) seolah-olah referensi secara signifikan lebih cepat ...

ircmaxell
sumber
1
Bukankah maksud Anda "optimasi [tidak direncanakan] adalah akar dari semua kejahatan"? ;) Nah semuanya adalah mereka melakukan hal yang sama, jadi itu bukan optimasi seperti itu: yang merupakan "cara standar yang lebih baik untuk diadopsi." Juga beberapa pertanyaan lagi yang belum terjawab: Anda mengatakan karena tidak perlu menyalin, tetapi bukankah penggunaan referensi juga overhead? komentar stillstanding dalam pertanyaan saya tampaknya tidak setuju dengan asumsi Anda juga. Juga, mengapa kode menghasilkan lebih lambat untuk referensi di sana juga. Apakah foreach berubah pada 5.3.0 untuk mengubah array () menjadi objek (mis. SplFixedArray)?
srcspider
@srcspider: Jawaban yang diedit dengan kode benchmark dan hasil yang menunjukkan referensi memang jauh lebih cepat daripada non-referensi ...
ircmaxell
1
@srcspider "the better standard way to adopt." performance bukan satu-satunya kriteria untuk memilih apa yang akan diadopsi. terutama dalam kasus yang tidak masuk akal. Terus terang, Anda hanya membuang-buang waktu
Your Common Sense
@Col. Pecahan peluru Saya setuju 100%. Keterbacaan dan pemeliharaan kinerja truf dengan selisih besar dalam kasus khusus ini ... Saya setuju tentang memilih standar dan tetap menggunakannya, tetapi mendasarkan standar itu pada faktor
ircmaxell
@ircmaxell: menjalankan skrip Anda dengan cepat tampaknya membuktikan maksud Anda, tetapi saya ingin memeriksanya sedikit lebih jauh; Saya dapat mengedit pertanyaan asli saya dengan lebih banyak teststo termasuk beberapa fitur 5.3 baru. @Col. Pecahan peluru: FOR adalah level program-kindergardn yang hampir universal, FOREACH adalah sintaksis yang lebih sederhana. Sejauh keterbacaan mereka tampaknya berada di tanah yang sama. Ini semua sangat rendah, saya tidak berpikir pemeliharaan adalah masalah seperti pada pola tingkat tinggi. Dan saya tidak berpikir saya menyia-nyiakan waktu saya karena "konstruksi dasar" ini akan menjelaskan banyak kode yang akan saya tulis. :)
srcspider
54

Saya tidak yakin ini sangat mengejutkan. Kebanyakan orang yang kode dalam PHP tidak berpengalaman dalam apa yang sebenarnya dilakukan PHP di bare metal. Saya akan menyatakan beberapa hal, yang sebagian besar akan benar:

  1. Jika Anda tidak mengubah variabel, nilai tambahan lebih cepat di PHP. Ini karena referensi itu tetap dihitung dan menurut nilai memberikan lebih sedikit untuk dilakukan. Ia tahu begitu Anda memodifikasi ZVAL (struktur data internal PHP untuk sebagian besar tipe), ia harus memutusnya secara langsung (salin dan lupakan ZVAL lainnya). Tapi Anda tidak pernah memodifikasinya, jadi tidak masalah. Referensi menjadikannya lebih rumit dengan lebih banyak pembukuan yang harus dilakukan untuk mengetahui apa yang harus dilakukan ketika Anda memodifikasi variabel. Jadi, jika Anda hanya-baca, secara paradoks lebih baik bukan intinya dengan &. Saya tahu, ini berlawanan dengan intuisi, tetapi juga benar.

  2. Foreach tidak lambat. Dan untuk iterasi sederhana, kondisi yang diuji terhadap - "apakah saya di akhir array ini" - dilakukan menggunakan kode asli, bukan opcode PHP. Bahkan jika itu opcodes yang di-cache APC, itu masih lebih lambat dari banyak operasi asli yang dilakukan di bare metal.

  3. Menggunakan for for "for ($ i = 0; $ i <count ($ x); $ i ++) lambat karena hitungan (), dan kurangnya kemampuan PHP (atau benar-benar bahasa apa pun yang ditafsirkan) untuk mengevaluasi pada parse waktu apakah ada yang memodifikasi array. Ini mencegahnya mengevaluasi hitungan sekali.

  4. Tetapi bahkan setelah Anda memperbaikinya dengan "$ c = hitung ($ x); untuk ($ i = 0; $ i <$ c; $ i ++) $ i <$ c adalah sekumpulan opcode Zend, seperti halnya $ i ++. Dalam perjalanan iterasi 100000, ini bisa berarti. Foreach tahu di tingkat asli apa yang harus dilakukan. Tidak perlu PHP opcodes untuk menguji kondisi "aku di akhir array ini".

  5. Bagaimana dengan "old school" (daftar ("barang? Nah, menggunakan masing-masing (), current (), dll. Semua akan melibatkan setidaknya 1 panggilan fungsi, yang tidak lambat, tetapi tidak gratis. Ya, itu adalah PHP opcodes lagi! Jadi, sementara + daftar + masing-masing memiliki biayanya juga.

Karena alasan ini, setiap langkah dapat dimengerti merupakan pilihan terbaik untuk iterasi sederhana.

Dan jangan lupa, ini juga yang paling mudah dibaca, jadi ini win-win.

Jaimie Sirovich
sumber
Inilah penjelasan yang saya cari, terima kasih.
penetasan
Jawaban ini harus benar-benar menjadi tambahan atau ringkasan untuk jawaban yang ditandai. Saya senang saya membacanya, kerja bagus.
doz87
30

Satu hal yang harus diperhatikan dalam tolok ukur (terutama phpbench.com), adalah meskipun angkanya baik, tes tidak. Banyak tes di phpbench.com melakukan hal-hal sepele dan menyalahgunakan kemampuan PHP untuk me-cache lookup array ke condong tolok ukur atau dalam kasus iterasi pada array tidak benar-benar mengujinya dalam kasus-kasus dunia nyata (tidak ada yang menulis kosong untuk loop). Saya telah melakukan tolok ukur sendiri yang saya temukan cukup mencerminkan hasil dunia nyata dan mereka selalu menunjukkan sintaks iterasi asli bahasa yang foreachkeluar di atas (kejutan, kejutan).

//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
Kendall Hopkins
sumber
3

Ini tahun 2020 dan banyak hal telah berkembang dengan php 7.4 dan opcache .

Berikut adalah patokan OP ^, dijalankan sebagai CLI unix , tanpa bagian gema dan html.

Tes dijalankan secara lokal di komputer biasa.

php -v

PHP 7.4.6 (cli) (built: May 14 2020 10:02:44) ( NTS )

Skrip benchmark yang dimodifikasi:

<?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";

Keluaran:

foreach 0.0032877922058105
foreach (using reference) 0.0029420852661133
for 0.0025191307067871
foreach (hash table) 0.0035080909729004
for (hash table) 0.0061779022216797

Seperti yang Anda lihat evolusi itu gila, sekitar 560 kali lebih cepat daripada yang dilaporkan pada 2012.

Di komputer dan server saya, mengikuti banyak percobaan saya, dasar-dasar untuk loop adalah yang tercepat. Ini bahkan lebih jelas menggunakan loop bersarang ( $ i $ j $ k ..)

Ini juga yang paling fleksibel dalam penggunaan, dan memiliki keterbacaan yang lebih baik dari pandangan saya.

NVRM
sumber
0

Saya pikir tetapi saya tidak yakin: forloop membutuhkan dua operasi untuk memeriksa dan menambah nilai. foreachmemuat data dalam memori kemudian akan mengulangi setiap nilai.

Eswer
sumber
7
Semua orang punya pendapat, orang-orang datang ke Stack Overflow untuk menemukan jawaban. Jika Anda tidak yakin dengan apa yang Anda nyatakan, periksa kode sumber, dokumentasinya, lakukan pencarian google, dll.
Sebastien F.
Karena kinerja didasarkan pada penelitian dan pengujian Anda harus mengeluarkan beberapa bukti. Harap berikan referensi Anda sesuai. Semoga Anda dapat meningkatkan jawaban Anda.
Marwan Salim
Saya pikir itu juga tergantung pada beban aktual server dan apa yang ingin Anda lakukan dalam loop. Saya pikir itu juga tergantung pada beban aktual server dan apa yang ingin Anda lakukan dalam loop. Saya ingin tahu apakah iterasi dari array bernomor saya sebaiknya menggunakan foreach - atau for-loop, jadi saya menjalankan benchmark di sandbox.onlinephpfunctions.com dengan PHP 7.4. Saya berulang kali menjalankan skrip yang sama berkali-kali dan setiap proses memberi saya hasil yang berbeda. Satu kali for-loop lebih cepat di lain waktu foreach-loop dan lain kali mereka sama.
Alexander Behling