PHP fonksiyonları için Big-O listesi


346

PHP'yi bir süre kullandıktan sonra, tüm yerleşik PHP işlevlerinin beklendiği kadar hızlı olmadığını fark ettim. Bir sayının önbelleğe alınmış bir primer dizisi kullanılarak birincil olup olmadığını bulan bir işlevin bu iki olası uygulamasını düşünün.

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

Bunun nedeni in_array, $prime_arraybüyüdükçe doğrusal olarak yavaşlayacak doğrusal bir arama O (n) ile uygulanmasıdır . Nerede array_key_existsfonksiyon karma tablo son derece nüfuslu verilmezse yavaşlatmak olmayacak bir karma arama O (1) ile uygulanır (bu durumda o birkaç O (n)).

Şimdiye kadar büyük O'ları deneme yanılma yoluyla keşfetmek zorunda kaldım ve zaman zaman kaynak koduna baktım . Şimdi soru için ...

Tüm yerleşik PHP işlevleri için teorik (veya pratik) büyük O zamanlarının bir listesi var mı?

* veya en azından ilginç olanlar

: Olası uygulama PHP bilinmeyen çekirdek veri yapıları bağlıdır çünkü Örneğin, ben sıralanan işlevlerin büyük O'yu tahmin etmek çok zor bir durum array_merge, array_merge_recursive, array_reverse, array_intersect, array_combine, str_replace(dizi girişli) vb


31
Tamamen konu dışı, ancak 1 asal değildir.
Jason Punyon

24
PHP dizileri hashtables. Bu size bilmeniz gereken her şeyi anlatmalıdır. Bir hashtable içindeki bir anahtarı aramak O (1) 'dir. Bir değeri aramak, sıralanmamış bir kümede yenemeyeceğiniz O (n) 'dir. Merak ettiğiniz işlevlerin çoğu muhtemelen O (n) şeklindedir. Tabii ki, gerçekten bilmek istiyorsanız, kaynağı okuyabilirsiniz: cvs.php.net/viewvc.cgi/php-src/ext/standard/…
Frank Farmer

11
Kayıt için, yapmaya çalıştığınız şeyin en hızlı uygulanması (değerleriniz için NULL kullanmak yerine) kullanmak trueve daha sonra varlığını kullanarak test etmek olacaktır isset($large_prime_array[$number]). Doğru hatırlarsam, in_arrayişlevden yüz kat daha hızlı olma sırasıdır .
mattbasta

3
Big O notasyonu hız ile ilgili değildir. Davranışı sınırlamakla ilgilidir.
Gumbo

3
@Kendall karşılaştırmıyorum array_key_exists, karşılaştırıyorum in_array. in_arraydizideki her öğeyi yineler ve değeri ona ilettiğiniz iğneyle karşılaştırır. Değerleri anahtara çevirirseniz (ve sadece değerlerin her birini kukla bir değerle değiştirirseniz true, kullanma işlemi issetbirçok kez daha hızlıdır. bu şekilde bir dizi hızda önemli bir iyileşme
gösterebilir

Yanıtlar:


650

Kimsenin bunu yapmış gibi görünmediğinden, bir yerde referans olması iyi bir fikir olacağını düşündüm. Gerçi ve array_*işlevleri karakterize etmek için benchmark veya kod gözden geçirme yoluyla gittim . Daha ilginç Big-O'yu tepeye koymaya çalıştım. Bu liste tamamlanmadı.

Not: Bir karma arama varsayılarak hesaplanan tüm Big-O, gerçekten O (n) olmasına rağmen O (1) 'dir. N'nin katsayısı çok düşüktür, yeterince büyük bir dizi saklamanın koç yükü, Big-O arama özelliklerinin etkili olmaya başlamasından önce size zarar verir. Örneğin array_key_exists, N = 1 ve N = 1.000.000'a yapılan bir çağrı arasındaki fark , zaman artışının ~% 50'sidir.

İlginç Noktalar :

  1. isset/ ve array_key_existsçok daha hızlıin_arrayarray_search
  2. +(union) biraz daha hızlı array_merge(ve daha güzel görünüyor). Ama farklı çalışıyor, bu yüzden aklınızda bulundurun.
  3. shuffle aynı Big-O katmanında array_rand
  4. array_pop/ yeniden endeks cezasından array_pushdaha hızlı array_shift/array_unshift

Aramalar :

array_key_existsO (n) ama gerçekten O (1) 'e yakın - bunun nedeni çarpışmalardaki doğrusal yoklamadır, ancak çarpışma şansı çok az olduğu için, katsayı da çok küçüktür. Daha gerçekçi bir big-O vermek için hash aramalarını O (1) olarak ele aldığınızı düşünüyorum. Örneğin, N = 1000 ve N = 100000 arasındaki fark sadece% 50 yavaşlamadır.

isset( $array[$index] )O (n) ancak gerçekten O (1) 'e yakın - array_key_exists ile aynı aramayı kullanır. Dil yapısı olduğundan, anahtar kodlanmışsa aramayı önbelleğe alır ve aynı anahtarın tekrar tekrar kullanıldığı durumlarda hızlanır.

in_array O (n) - bunun nedeni, değeri bulana kadar dizi üzerinden doğrusal bir arama yapmasıdır.

array_search O (n) - in_array ile aynı çekirdek işlevini kullanır, ancak değer döndürür.

Kuyruk fonksiyonları :

array_push O (i var_i, tüm i için)

array_pop O (1)

array_shift O (n) - tüm anahtarları yeniden endekslemeli

array_unshift O (n + ∑ var_i, tüm i için) - tüm anahtarları yeniden endekslemek zorundadır

Dizi Kavşağı, Birlik, Çıkarma :

array_intersect_key kesişme% 100 ise O (Maks (param_i_size) * ∑param_i_count, tüm i için), kesişme% 0 O ile kesişirse (iparam_i_size, tüm i için)

array_intersect kavşak% 100 ise O (n ^ 2 * ∑param_i_count, tüm i için) yaparsa, kavşak% 0 O (n ^ 2) ile kesişirse

array_intersect_assoc kesişme% 100 ise O (Maks (param_i_size) * ∑param_i_count, tüm i için), kesişme% 0 O ile kesişirse (iparam_i_size, tüm i için)

array_diff O (i param_i_size, tüm i için) - Bu tüm param_sizes ürünü

array_diff_key O (∑ param_i_size, çünkü i! = 1) - bunun nedeni, ilk dizi üzerinde yineleme yapmamıza gerek olmamasıdır.

array_merge O (∑ dizi_i, i! = 1) - ilk dizi üzerinde yineleme yapmaya gerek yok

+ (birleşim) O (n); burada n, 2. dizinin boyutudur (yani dizi_first + dizi_second) - yeniden numaralandırması gerekmediği için array_merge'den daha az ek yük

array_replace O (i dizi_i, tüm i için)

Rastgele :

shuffle O (n)

array_rand O (n) - Doğrusal bir anket gerektirir.

Açıkça Big-O :

array_fill O (n)

array_fill_keys O (n)

range O (n)

array_splice O (ofset + uzunluk)

array_slice O = ofset + uzunluk) veya uzunluk = NULL ise O (n)

array_keys O (n)

array_values O (n)

array_reverse O (n)

array_pad O (pad_size)

array_flip O (n)

array_sum O (n)

array_product O (n)

array_reduce O (n)

array_filter O (n)

array_map O (n)

array_chunk O (n)

array_combine O (n)

Eureqa'ya fonksiyonların Big-O'yu bulmayı kolaylaştırdığı için teşekkür etmek istiyorum . Keyfi veriler için en uygun işlevi bulabilen inanılmaz bir ücretsiz programdır.

DÜZENLE:

PHP dizi aramaları olduğundan şüphe duyanlar için O(N), bunu test etmek için bir kriter yazdım (hala O(1)en gerçekçi değerler için etkili ).

php dizi arama grafiği

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

5
@Kendall: Teşekkürler! Ben biraz okuma yaptım ve PHP çarpışmalar için 'iç içe' hashtables kullanır çıkıyor. Yani, çarpışmalar için bir kütük yapısı yerine başka bir hashtable kullanır. Ve pratik olarak konuşan PHP hashtable'larının O (1) performansı veya ortalama olarak en az O (1) verdiğini anlıyorum - hashtables bunun için. Sadece neden "gerçekten O (logn)" değil de "gerçekten O (n)" olduğunu söylediğinizi merak ediyordum. Bu arada harika gönderi!
Cam

10
Zaman karmaşıklıkları belgelere dahil edilmelidir! Doğru işlevi seçmek size çok zaman kazandırabilir veya planladığınız şeyi yapmaktan kaçınmanızı söyleyebilir: p Bu liste için şimdiden teşekkürler!
Samuel

41
Bunun eski olduğunu biliyorum ... ama ne? Bu eğri hiç O (n) göstermez, O (log n), en.wikipedia.org/wiki/Logarithm gösterir . Hangi iç içe karma haritalar için beklediğiniz ile de doğrudur.
Andreas

5
Bir dizinin elemanındaki ayarlanmamış Big-O nedir?
Chandrew

12
Karma tablolar gerçekten en kötü durum O (n) arama karmaşıklığına sahip olsa da, ortalama durum O (1) ve karşılaştırmalı testinizin test ettiği özel durum sıfır (sıfır), sürekli, sayısal olarak endekslenmiş olduğundan garanti edilir (O (1)). hiçbir zaman karma çarpışmalar olmayacak. Hala dizi boyutuna bağımlılık görmenizin nedeni algoritmik karmaşıklık ile ilgisi yoktur, CPU önbellek efektlerinden kaynaklanır. Dizi ne kadar büyük olursa, rasgele erişimli aramaların önbellek hatalarına (ve hiyerarşide önbellek daha fazla özlüyor) neden olma olasılığı o kadar yüksektir.
NikiC

5

Özel olarak açıkladığınız durumun açıklaması ilişkilendirilebilir dizilerin karma tablolar olarak uygulanmasıdır - bu nedenle anahtarla (ve buna karşılık olarak array_key_exists) arama O (1) 'dir. Ancak, diziler değere göre dizine eklenmez, bu nedenle genel durumda dizide bir değerin var olup olmadığını keşfetmenin tek yolu doğrusal bir aramadır. Orada sürpriz yok.

PHP yöntemlerinin algoritmik karmaşıklığının özel kapsamlı belgeleri olduğunu sanmıyorum. Ancak, çabayı garanti etmek için yeterince büyük bir endişe varsa, her zaman kaynak kodunu inceleyebilirsiniz .


Bu gerçekten bir cevap değil. Soruda belirttiğim gibi, zaten PHP kaynak kodunu aramaya çalıştım. PHP uygulandığından, karmaşık makrolardan yararlanılarak yazılmıştır, bu da zaman zaman fonksiyonların altında yatan büyük O'yu "görmeyi" zorlaştırabilir.
Kendall Hopkins

@Kendall Kaynak koduna dalma referansınızı göz ardı ettim. Ancak, cevabımda bir cevap var: "PHP yöntemlerinin algoritmik karmaşıklığının özel kapsamlı belgeleri olduğunu sanmıyorum." "Hayır" tamamen geçerli bir cevaptır. (c:
Dathan

4

Bunun issetyerine neredeyse her zaman kullanmak istersiniz array_key_exists. Ben iç bakmak değil, ama eminim array_key_existsO (N) eminim çünkü dizi her anahtar üzerinde yinelenen, isseteriştiğinizde kullanılan aynı karma algoritma kullanarak öğeye erişmeye çalışırken dizi dizini. Bu O (1) olmalıdır.

Dikkat edilmesi gereken bir "yakaladım" şudur:

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

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

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

Merak ettim, bu yüzden farkı karşılaştırdım:

<?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 saniye
array_key_exists:2.33202195168 saniye

Tabii ki, bu zaman karmaşıklığını göstermez, ancak 2 fonksiyonun birbiriyle nasıl karşılaştırıldığını gösterir.

Zaman karmaşıklığını test etmek için, bu işlevlerden birini ilk tuşta ve son tuşta çalıştırmak için gereken süreyi karşılaştırın.


9
Bu yanlış. Array_key_exists her anahtar üzerinde yineleme yapmak zorunda olmadığından% 100 eminim. Eğer inanmıyorsanız aşağıdaki bağlantıya bir göz atın. İssetin çok daha hızlı olmasının nedeni, bir dil kurumu olması. Bu da işlev çağrısı yapma yükü olmadığı anlamına gelir. Ayrıca, bu nedenle aramayı önbelleğe alıyor olabileceğini düşünüyorum. Ayrıca, bu SORU için bir cevap değildir! Ben (soru durumlar gibi) PHP fonksiyonları için Büyük (O) bir listesini istiyorum. Örneklerimin tek bir ölçütü değil. svn.php.net/repository/php/php-src/branches/PHP_5_3/ext/…
Kendall Hopkins

Bana hala inanmıyorsanız, noktayı göstermek için küçük bir kriter oluşturuyorum. pastebin.com/BdKpNvkE
Kendall Hopkins

Karşılaştırmanızda yanlış olan şey, xdebug'u devre dışı bırakmanız gerektiğidir. =)
Guilherme Blanco

3
Array_key_exists üzerinde isset'i kullanmak istemenizin iki önemli nedeni vardır. İlk olarak, isset bir işlev çağrısının maliyetini azaltan bir dil yapısıdır. Bu, $arrray[] = $appendvs array_push($array, $append)argümanına benziyor . İkinci olarak, array_key_exists ayrıca ayarlanmamış ve null değerler arasında ayrım yapar. Çünkü $a = array('fred' => null); array_key_exists('fred', $a)doğru isset($['fred'])dönerken yanlış döner. Bu ekstra adım önemsiz değildir ve yürütme süresini büyük ölçüde artıracaktır.
Ocak'ta orca

0

İnsanlar anahtar çarpışmalarla pratikte sorun yaşarlarsa, ikincil karma arama veya dengeli bir ağaç içeren kaplar uygularlardı. Dengeli ağaç O (log n) 'ye en kötü durum davranışı ve O (1) ort. durum (karma değeri). Bellek uygulamalarında en pratikte buna değmez, ancak belki de bu karışık strateji biçimini varsayılan durumları olarak uygulayan veritabanları vardır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.