Daftar Big-O untuk fungsi PHP

345

Setelah menggunakan PHP untuk sementara waktu sekarang, saya perhatikan bahwa tidak semua fungsi PHP built-in secepat yang diharapkan. Pertimbangkan dua kemungkinan implementasi dari fungsi ini yang menemukan jika suatu bilangan prima menggunakan array bilangan prima yang di-cache

//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 );
}

Ini karena in_arraydiimplementasikan dengan pencarian linear O (n) yang secara linear akan melambat seiring dengan $prime_arraypertumbuhan. Di mana array_key_existsfungsi diimplementasikan dengan pencarian hash O (1) yang tidak akan melambat kecuali tabel hash menjadi sangat padat (dalam hal ini hanya O (n)).

Sejauh ini saya harus menemukan big-O melalui trial and error, dan sesekali melihat kode sumber . Sekarang untuk pertanyaan ...

Apakah ada daftar waktu O besar teoritis (atau praktis) untuk semua * fungsi PHP bawaan?

* atau setidaknya yang menarik

Sebagai contoh, saya merasa sangat sulit untuk memprediksi O besar fungsi yang terdaftar karena kemungkinan pelaksanaan tergantung pada struktur data inti yang tidak diketahui dari PHP: array_merge, array_merge_recursive, array_reverse, array_intersect, array_combine, str_replace(dengan input array), dll

Kendall Hopkins
sumber
31
Benar-benar keluar dari topik tetapi, 1 tidak prima.
Jason Punyon
24
Array dalam PHP adalah hashtable. Itu akan memberi tahu Anda segala yang perlu Anda ketahui. Mencari kunci dalam hashtable adalah O (1). Mencari nilai adalah O (n) - yang tidak bisa Anda kalahkan pada set yang tidak disortir. Sebagian besar fungsi yang membuat Anda penasaran mungkin O (n). Tentu saja, jika Anda benar-benar ingin tahu, Anda dapat membaca sumbernya: cvs.php.net/viewvc.cgi/php-src/ext/standard/…
Frank Farmer
11
Sebagai catatan, implementasi tercepat dari apa yang Anda coba lakukan adalah menggunakan (alih-alih menggunakan NULL untuk nilai Anda) menggunakan truedan kemudian menguji keberadaan menggunakan isset($large_prime_array[$number]). Jika saya ingat dengan benar, urutannya ratusan kali lebih cepat daripada in_arrayfungsinya.
mattbasta
3
Notasi Big O bukan tentang kecepatan. Ini tentang membatasi perilaku.
Gumbo
3
@ Kendall Saya tidak membandingkan array_key_exists, saya membandingkan in_array. in_arrayiterates setiap item dalam array dan membandingkan nilai dengan jarum yang Anda berikan. Jika Anda membalikkan nilai ke kunci (dan hanya mengganti masing-masing nilai dengan nilai dummy seperti true, menggunakan issetbanyak kali lebih cepat. Ini karena kunci array diindeks oleh PHP (seperti hashtable). Akibatnya, mencari sebuah array dengan cara ini dapat meningkatkan kecepatan secara signifikan
mattbasta

Jawaban:

649

Karena sepertinya tidak ada yang melakukan ini sebelum saya pikir itu ide yang baik untuk memilikinya untuk referensi di suatu tempat. Saya sudah pergi dan baik melalui benchmark atau skimming kode untuk mengkarakterisasi array_*fungsi. Saya sudah mencoba menempatkan Big-O yang lebih menarik di dekat bagian atas. Daftar ini tidak lengkap.

Catatan: Semua Big-O yang dihitung dengan asumsi pencarian hash adalah O (1) meskipun itu benar-benar O (n). Koefisien dari n sangat rendah, overhead ram menyimpan array yang cukup besar akan menyakiti Anda sebelum karakteristik pencarian Big-O akan mulai berlaku. Misalnya perbedaan antara panggilan ke array_key_existsdi N = 1 dan N = 1.000.000 adalah ~ 50% peningkatan waktu.

Poin Menarik :

  1. issetSaya array_key_existsjauh lebih cepat daripada in_arraydanarray_search
  2. +(Serikat pekerja) sedikit lebih cepat daripada array_merge(dan terlihat lebih bagus). Tapi itu bekerja secara berbeda jadi ingatlah itu.
  3. shuffle berada pada tingkat Big-O yang sama dengan array_rand
  4. array_pop/ array_pushlebih cepat dari array_shift/ array_unshiftkarena penalti indeks ulang

Pencarian :

array_key_existsO (n) tetapi sangat dekat dengan O (1) - ini karena jajak pendapat linear dalam tabrakan, tetapi karena kemungkinan tabrakan sangat kecil, koefisiennya juga sangat kecil. Saya menemukan Anda memperlakukan pencarian hash sebagai O (1) untuk memberikan O-besar yang lebih realistis. Misalnya perbedaan antara N = 1000 dan N = 100000 hanya sekitar 50% melambat.

isset( $array[$index] )O (n) tetapi sangat dekat dengan O (1) - ia menggunakan pencarian yang sama seperti array_key_exists. Karena konstruk bahasanya, akan men-cache pencarian jika kunci dikodekan, sehingga mempercepat dalam kasus di mana kunci yang sama digunakan berulang kali.

in_array O (n) - ini karena ia melakukan pencarian linear melalui array sampai menemukan nilainya.

array_search O (n) - ia menggunakan fungsi inti yang sama dengan in_array tetapi mengembalikan nilai.

Fungsi antrian :

array_push O (∑ var_i, untuk semua i)

array_pop O (1)

array_shift O (n) - itu harus mengindeks ulang semua kunci

array_unshift O (n + ∑ var_i, untuk semua i) - ia harus mengindeks ulang semua kunci

Array Intersection, Union, Subtraction :

array_intersect_key jika persimpangan 100% lakukan O (Maks (param_i_size) * param_i_count, untuk semua i), jika persimpangan 0% berpotongan O (∑param_i_size, untuk semua i)

array_intersect jika persimpangan 100% lakukan O (n ^ 2 * param_i_count, untuk semua i), jika persimpangan 0% berpotongan O (n ^ 2)

array_intersect_assoc jika persimpangan 100% lakukan O (Maks (param_i_size) * param_i_count, untuk semua i), jika persimpangan 0% berpotongan O (∑param_i_size, untuk semua i)

array_diff O (π param_i_size, for all i) - Itu adalah produk dari semua param_sizes

array_diff_key O (∑ param_i_size, untuk i! = 1) - ini karena kita tidak perlu mengulangi array pertama.

array_merge O (∑ array_i, i! = 1) - tidak perlu mengulangi array pertama

+ (union) O (n), di mana n adalah ukuran array ke-2 (yaitu array_first + array_second) - lebih sedikit overhead daripada array_merge karena tidak harus dinomori ulang

array_replace O (∑ array_i, untuk semua i)

Acak :

shuffle Di)

array_rand O (n) - Membutuhkan jajak pendapat linier.

Jelas Big-O :

array_fill Di)

array_fill_keys Di)

range Di)

array_splice O (offset + panjang)

array_slice O (offset + panjang) atau O (n) jika panjang = NULL

array_keys Di)

array_values Di)

array_reverse Di)

array_pad O (pad_size)

array_flip Di)

array_sum Di)

array_product Di)

array_reduce Di)

array_filter Di)

array_map Di)

array_chunk Di)

array_combine Di)

Saya ingin berterima kasih kepada Eureqa karena membuatnya mudah menemukan Big-O dari fungsinya. Ini adalah program gratis luar biasa yang dapat menemukan fungsi pemasangan terbaik untuk data sewenang-wenang.

EDIT:

Bagi mereka yang meragukan bahwa pencarian array PHP O(N), saya telah menulis benchmark untuk menguji itu (mereka masih efektif O(1)untuk nilai-nilai paling realistis).

grafik pencarian array php

$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 );
}
Kendall Hopkins
sumber
5
@ Kendall: Terima kasih! Saya melakukan sedikit pembacaan dan ternyata PHP menggunakan hashtable 'bersarang' untuk tabrakan. Artinya, alih-alih struktur logn untuk tabrakan itu hanya menggunakan hashtable lain. Dan saya mengerti bahwa secara praktis hashtable PHP memberikan kinerja O (1), atau setidaknya rata-rata O (1) - itulah gunanya hashtables. Saya hanya ingin tahu mengapa Anda mengatakan mereka "benar-benar O (n)" dan bukan "benar-benar O (logn)". Pos yang bagus!
Kamera
10
Kompleksitas waktu harus disertakan dengan dokumentasi! Memilih fungsi yang tepat dapat menghemat banyak waktu, atau memberitahu Anda untuk menghindari melakukan apa yang Anda rencanakan: p Terima kasih atas daftar ini!
Samuel
41
Saya tahu ini sudah tua ... tapi apa? Kurva itu sama sekali tidak menunjukkan O (n), itu menunjukkan O (log n), en.wikipedia.org/wiki/Logarithm . Yang juga akurat dengan apa yang Anda harapkan untuk hash-maps bersarang.
Andreas
5
Apakah Big-O yang tidak disetel pada elemen array?
Chandrew
12
Walaupun hashtable memang memiliki kompleksitas pencarian kasus O (n) terburuk, kasus rata-rata adalah O (1) dan kasus khusus yang dijadikan tolok ukur pengujian Anda dijamin O (1), karena ini merupakan zero-based, kontinu, diindeks secara numerik array, yang tidak akan pernah memiliki tabrakan hash. Alasan mengapa Anda masih melihat ketergantungan pada ukuran array tidak ada hubungannya dengan kompleksitas algoritmik, ini disebabkan oleh efek cache CPU. Semakin besar arraynya, semakin besar kemungkinan pencarian akses acak akan menghasilkan cache cache (dan cache cache lebih tinggi dalam hierarki).
NikiC
5

Penjelasan untuk kasus yang Anda gambarkan secara spesifik adalah bahwa array asosiatif diimplementasikan sebagai tabel hash - jadi pencarian dengan kunci (dan juga, array_key_exists) adalah O (1). Namun, array tidak diindeks oleh nilai, jadi satu-satunya cara dalam kasus umum untuk menemukan apakah suatu nilai ada dalam array adalah pencarian linear. Tidak ada kejutan di sana.

Saya tidak berpikir ada dokumentasi komprehensif khusus tentang kompleksitas algoritmik metode PHP. Namun, jika ini masalah yang cukup besar untuk menjamin upaya tersebut, Anda selalu dapat melihat kode sumbernya .

Dathan
sumber
Ini sebenarnya bukan jawaban. Seperti yang telah saya nyatakan dalam pertanyaan, saya sudah mencoba melihat ke dalam kode sumber PHP. Karena PHP diimplementasikan ditulis dalam C menggunakan macro kompleks, yang dapat mempersulit "melihat" O besar yang mendasari fungsi.
Kendall Hopkins
@ Kendall Saya mengabaikan referensi Anda untuk menyelam ke dalam kode sumber. Namun, ada jawaban dalam jawaban saya: "Saya rasa tidak ada dokumentasi komprehensif khusus tentang kompleksitas algoritmik metode PHP." "Tidak" adalah jawaban yang benar-benar valid. (c:
Dathan
4

Anda hampir selalu ingin menggunakan issetbukan array_key_exists. Saya tidak melihat internal, tapi saya cukup yakin itu array_key_existsadalah O (N) karena iterates atas masing-masing dan setiap kunci array, sementara issetmencoba mengakses elemen menggunakan algoritma hash yang sama yang digunakan ketika Anda mengakses indeks array. Itu harus O (1).

Satu "gotcha" yang harus diperhatikan adalah ini:

$search_array = array('first' => null, 'second' => 4);

// returns false
isset($search_array['first']);

// returns true
array_key_exists('first', $search_array);

Saya ingin tahu, jadi saya membandingkan perbedaannya:

<?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 detik
array_key_exists:2,33202195168 detik

Tentu saja, ini tidak menunjukkan kompleksitas waktu, tetapi ini menunjukkan bagaimana kedua fungsi tersebut saling membandingkan.

Untuk menguji kompleksitas waktu, bandingkan jumlah waktu yang diperlukan untuk menjalankan salah satu fungsi ini pada tombol pertama dan terakhir.

ryeguy
sumber
9
Ini salah. Saya 100% yakin array_key_exists tidak perlu mengulangi setiap kunci. Jika Anda tidak percaya, lihat tautan di bawah ini. Alasan penerbit jauh lebih cepat adalah karena ini merupakan konstruksi bahasa. Yang berarti tidak memiliki overhead untuk melakukan panggilan fungsi. Juga, saya pikir mungkin caching pencarian, karena ini. Juga, ini bukan jawaban untuk PERTANYAAN! Saya ingin daftar Big (O) untuk fungsi PHP (seperti yang dinyatakan pertanyaan). Bukan tolok ukur tunggal dari contoh saya. svn.php.net/repository/php/php-src/branches/PHP_5_3/ext/…
Kendall Hopkins
Jika Anda masih tidak percaya, saya telah membuat tolok ukur kecil untuk menunjukkan poinnya. pastebin.com/BdKpNvkE
Kendall Hopkins
Apa yang salah dengan tolok ukur Anda adalah Anda harus menonaktifkan xdebug. =)
Guilherme Blanco
3
Ada dua alasan penting mengapa Anda ingin menggunakan isset over array_key_exists. Pertama, isset adalah konstruk bahasa yang mengurangi biaya pemanggilan fungsi. Ini mirip dengan argumen $arrray[] = $appendvs. array_push($array, $append)Kedua, array_key_exists juga membedakan antara nilai yang tidak disetel dan null. Untuk$a = array('fred' => null); array_key_exists('fred', $a) akan mengembalikan benar sementara isset($['fred'])akan mengembalikan salah. Langkah ekstra ini tidak sepele dan akan sangat meningkatkan waktu eksekusi.
orca
0

Jika orang-orang mengalami kesulitan dalam praktek dengan tabrakan kunci, mereka akan menerapkan kontainer dengan pencarian hash sekunder atau pohon seimbang. Pohon seimbang akan memberikan O (log n) perilaku kasus terburuk dan O (1) rata-rata. case (hash itu sendiri). Overhead tidak sepadan dengan yang paling praktis dalam aplikasi memori, tetapi mungkin ada database yang menerapkan bentuk strategi campuran ini sebagai kasus default mereka.

Josh Stern
sumber