Özyineleme döngüden daha hızlı mı?


286

Özyinelemenin bazen döngüden çok daha temiz olduğunu biliyorum ve yineleme yinelemeyi ne zaman kullanmam gerektiği hakkında bir şey sormuyorum, zaten bununla ilgili birçok soru olduğunu biliyorum.

Sorduğum şey , özyineleme bir döngüden daha hızlı mı? Bana göre, her zaman bir döngüyü iyileştirebilir ve yinelemeli bir işlevden daha hızlı performans göstermesini sağlayabilirsiniz, çünkü döngü sürekli olarak yeni yığın kareleri kurmaz.

Özellikle, bazı sıralama işlevlerinde, ikili ağaçlarda vb.Gibi özyinelemenin verileri işlemek için doğru yol olduğu uygulamalarda özyinelemenin daha hızlı olup olmadığını arıyorum.


3
Bazen yinelemeli prosedür veya bazı nüksler için kapalı formlu formüllerin ortaya çıkması yüzyıllar alabilir. Ben sadece o zamanlar özyineleme daha hızlı :) lol
Pratik Deoghare

24
Kendi adıma, yinelemeyi tercih ederim. ;-)
Iterator



@PratikDeoghare Hayır, soru tamamen farklı bir algoritma seçmekle ilgili değil. Yinelemeli bir işlevi her zaman döngü kullanan aynı şekilde çalışan bir yönteme dönüştürebilirsiniz. Örneğin, bu cevap hem özyinelemeli hem de döngü biçiminde aynı algoritmaya sahiptir . Genel olarak, özyinelemeli işleve ilişkin argümanların bir demetini yığına koyar, çağırmak için yığına iter, işlevden dönmek için yığından atarsınız.
TamaMcGlinn

Yanıtlar:


358

Bu, kullanılan dile bağlıdır. 'Dil-agnostik' yazdınız, bu yüzden bazı örnekler vereceğim.

Java, C ve Python'da özyineleme, yineleme (genel olarak) ile karşılaştırıldığında oldukça pahalıdır, çünkü yeni bir yığın çerçevesinin tahsis edilmesini gerektirir. Bazı C derleyicilerinde, bu ek yükü ortadan kaldırmak için, belirli özyineleme türlerini (aslında, belirli kuyruk çağrıları türleri) işlev çağrıları yerine atlamalara dönüştüren bir derleyici bayrağı kullanılabilir.

İşlevsel programlama dili uygulamalarında, bazen yineleme çok pahalı olabilir ve özyineleme çok ucuz olabilir. Birçoğunda, özyineleme basit bir sıçramaya dönüştürülür, ancak döngü değişkenini değiştirmek (değiştirilebilir) bazen , özellikle birden fazla yürütme iş parçacığını destekleyen uygulamalarda nispeten ağır işlemler gerektirir. Mutasyon ve çöp toplayıcı arasındaki etkileşim nedeniyle mutasyon bu ortamların bazılarında pahalıdır, eğer ikisi de aynı anda çalışıyorsa.

Bazı Şema uygulamalarında, özyinelemenin genellikle döngüden daha hızlı olacağını biliyorum.

Kısacası, cevap koda ve uygulamaya bağlıdır. Tercih ettiğiniz stili kullanın. İşlevsel bir dil kullanıyorsanız, özyineleme daha hızlı olabilir . Zorunlu bir dil kullanıyorsanız, yineleme muhtemelen daha hızlıdır. Bazı ortamlarda, her iki yöntem de aynı montajın oluşturulmasına neden olur (bunu borunuza koyun ve dumanlayın).

Zeyilname: Bazı ortamlarda en iyi alternatif, özyineleme veya yineleme değil, daha üst düzey işlevlerdir. Bunlar arasında "harita", "filtre" ve "azaltma" ("katlama" olarak da bilinir) bulunur. Bunlar sadece tercih edilen stil değil, aynı zamanda genellikle daha temiz olmakla kalmaz, aynı zamanda bu işlevler otomatik paralelleştirmeden bir artış sağlayan ilk (veya yalnızca) işlevdir - böylece yineleme veya özyinelemeden önemli ölçüde daha hızlı olabilirler. Veri Paralel Haskell böyle bir ortama örnektir.

Liste kavramaları başka bir alternatiftir, ancak bunlar genellikle yineleme, özyineleme veya daha yüksek dereceli işlevler için sözdizimsel şekerdir.


48
Bunu + 1'ledim ve "özyineleme" ve "döngüler" in sadece insanların kodlarını söylediklerini yorumlamak istiyorum. Performans için önemli olan şeylere nasıl isim verdiğiniz değil, nasıl derlendikleri / yorumlandıklarıdır. Özyineleme, tanım gereği, matematiksel bir kavramdır ve yığın çerçeveleri ve montaj şeyleriyle ilgisi yoktur.
P

1
Ayrıca, özyineleme genel olarak işlevsel dillerde daha doğal bir yaklaşımdır ve yineleme zorunlu dillerde normalde daha sezgiseldir. Performans farkının farkedilmesi olası değildir, bu yüzden sadece belirli bir dil için daha doğal hissettiren şeyleri kullanın. Örneğin, özyineleme çok daha basit olduğunda, Haskell'de yinelemeyi kullanmak istemezsiniz.
Sasha Chedygov

4
Genellikle özyineleme döngülere derlenir , döngüler daha düşük düzeyli bir yapıdır. Neden? Özyineleme, tipik olarak bazı veri yapısı üzerinde iyi kurulmuş olup, bir İlk F cebiri indükler ve (özyinelemeli) hesaplamanın yapısı hakkında tümevarımsal argümanlarla birlikte sonlandırma ile ilgili bazı özellikleri kanıtlamanızı sağlar. Özyinelemeyi döngülere derleme işlemi kuyruk çağrısı optimizasyonudur.
Kristopher Micinski

En önemli şey, yapılmayan işlemler. Ne kadar fazla "IO", o kadar çok işlemeniz gerekir. Un-IOing verileri (aka indeksleme) her zaman en büyük performans artışıdır çünkü ilk etapta işlemek zorunda kalmazsınız.
Jeff Fischer

53

özyineleme bir döngüden daha hızlı mı?

Hayır, yineleme her zaman Yineleme'den daha hızlı olacaktır. (Von Neumann Mimarisinde)

Açıklama:

Genel bir bilgisayarın minimum işlemlerini sıfırdan oluşturursanız, "Yineleme" önce bir yapı taşı olarak gelir ve "özyineleme" den daha az kaynak yoğundur, ergo daha hızlıdır.

Sıfırdan bir sahte bilgisayar makinesi inşa etmek:

Kendinize soru sorun : Bir değeri hesaplamak , yani bir algoritmayı izlemek ve bir sonuca ulaşmak için neye ihtiyacınız var ?

Sıfırdan başlayıp temelde temel, temel kavramları tanımlayarak kavramlardan oluşan bir hiyerarşi oluşturacağız, sonra bunlarla ikinci seviye kavramlar oluşturacağız vb.

  1. İlk Konsept: Bellek hücreleri, depolama, Durum . Bir şey yapmak için nihai ve ara sonuç değerlerini saklayabileceğiniz yerlere ihtiyacınız vardır . Diyelim ki Memory , M [0..Infinite] adlı sonsuz bir "tamsayı" hücre dizimiz var.

  2. Talimatlar: bir şeyler yapın - bir hücreyi dönüştürün, değerini değiştirin. durumu değiştir . Her ilginç talimat bir dönüşüm gerçekleştirir. Temel talimatlar:

    a) Bellek hücrelerini ayarlama ve taşıma

    • hafızaya bir değer kaydetme, örneğin: 5 m kaydetme [4]
    • bir değeri başka bir konuma kopyalayın: örneğin: store m [4] m [8]

    b) Mantık ve aritmetik

    • ve, veya xor, değil
    • add, sub, mul, div. örneğin m [7] m [8] ekleyin
  3. Bir Yürütücü Ajan : Modern bir CPU'nun çekirdeği . "Aracı", talimatları yürütebilecek bir şeydir. Bir Ajan aynı zamanda kağıt üzerindeki algoritmayı takip eden bir kişi olabilir.

  4. Adım sırası: bir talimatlar dizisi : yani: önce yapın, sonra yapın, vb. Zorunlu bir talimatlar dizisi. Bir satır ifadesi bile "zorunlu talimat dizisidir". Belirli bir "değerlendirme sırasına" sahip bir ifadeniz varsa adımlarınız vardır . Bu, tek bir bestelenmiş ifadenin bile örtük “adımlara” ve ayrıca örtük bir yerel değişkene sahip olduğu anlamına gelir (buna “sonuç” diyelim). Örneğin:

    4 + 3 * 2 - 5
    (- (+ (* 3 2) 4 ) 5)
    (sub (add (mul 3 2) 4 ) 5)  
    

    Yukarıdaki ifade, örtük bir "sonuç" değişkeniyle 3 adım anlamına gelir.

    // pseudocode
    
           1. result = (mul 3 2)
           2. result = (add 4 result)
           3. result = (sub result 5)
    

    Dolayısıyla, belirli bir değerlendirme sırasına sahip olduğunuzdan, ifadeler bile, zorunlu bir talimat dizisidir . İfade , belirli bir sırayla yapılacak bir dizi işlem anlamına gelir ve adımlar olduğundan, örtük bir "sonuç" ara değişkeni de vardır.

  5. Yönerge İşaretçisi : Bir dizi adımınız varsa, örtük bir "yönerge işaretçisi" de vardır. Talimat işaretçisi bir sonraki talimatı işaretler ve talimat okunduktan sonra ancak talimat yürütülmeden önce ilerler.

    Bu sahte bilgisayar makinesinde, Yönerge İşaretçisi Belleğin bir parçasıdır . (Not: Normalde Yönerge İşaretçisi CPU çekirdeğinde “özel kayıt” olacaktır, ancak burada kavramları basitleştireceğiz ve tüm verilerin (kayıtlar dahil) “Hafıza” nın bir parçası olduğunu varsayacağız)

  6. Atla - Sıralı sayıda adımınız ve bir Talimat İşaretçisi varsa , Talimat İşaretçisinin değerini değiştirmek için " mağaza " talimatını uygulayabilirsiniz . Mağaza talimatının bu özel kullanımını yeni bir adla adlandıracağız: Jump . Yeni bir isim kullanıyoruz çünkü yeni bir kavram olarak düşünmek daha kolay. Talimat işaretçisini değiştirerek aracıya “adım x'e git” talimatını veriyoruz.

  7. Sonsuz Yineleme : Geriye atlayarak , ajanın belirli sayıda adımı "tekrar etmesini" sağlayabilirsiniz. Bu noktada sonsuz İterasyonumuz var.

                       1. mov 1000 m[30]
                       2. sub m[30] 1
                       3. jmp-to 2  // infinite loop
    
  8. Koşullu - Talimatların koşullu yürütülmesi. "Koşullu" yan tümcesinde, geçerli duruma bağlı olarak (önceki bir talimatla ayarlanabilir) birkaç komuttan birini koşullu olarak yürütebilirsiniz.

  9. Doğru İterasyon : Şimdi şartlı madde ile geri atlama talimatının sonsuz döngüsünden kaçabiliriz . Şimdi koşullu bir döngüye sahibiz ve sonra uygun yineleme

    1. mov 1000 m[30]
    2. sub m[30] 1
    3. (if not-zero) jump 2  // jump only if the previous 
                            // sub instruction did not result in 0
    
    // this loop will be repeated 1000 times
    // here we have proper ***iteration***, a conditional loop.
    
  10. Adlandırma : veri tutan veya bir adım tutan belirli bir bellek konumuna ad vermek . Bu sadece bir "kolaylık" olması. Bellek konumları için “adlar” tanımlama kapasitesine sahip olarak yeni talimatlar eklemiyoruz. “Adlandırma” ajan için bir talimat değil, sadece bizim için bir kolaylık. Adlandırma , kodu (bu noktada) okumayı ve değiştirmeyi kolaylaştırır.

       #define counter m[30]   // name a memory location
       mov 1000 counter
    loop:                      // name a instruction pointer location
        sub counter 1
        (if not-zero) jmp-to loop  
    
  11. Tek düzey alt program : Sıklıkla yürütmeniz gereken bir dizi adım olduğunu varsayalım. Adımları bellekte adlandırılmış bir konumda saklayabilir ve yürütmeniz gerektiğinde bu konuma atlayabilirsiniz (çağrı). Dizinin sonunda, yürütmeye devam etmek için arama noktasına geri dönmeniz gerekir . Bu mekanizma ile temel talimatları oluşturarak yeni talimatlar (altyordamlar) oluşturuyorsunuz.

    Uygulama: (yeni konsept gerekmez)

    • Geçerli Yönerge İşaretçisini önceden tanımlanmış bir bellek konumunda saklayın
    • altyordamına atla
    • altyordamın sonunda, Yönerge İşaretçisini önceden tanımlanmış bellek konumundan alır ve orijinal aramanın aşağıdaki komutuna etkili bir şekilde geri dönersiniz

    İlgili sorun tek seviyeli uygulaması: Bir sabrutinden başka alt yordamı çağırmak olamaz. Bunu yaparsanız, dönen adresin üzerine yazılır (global değişken), böylece aramaları iç içe geçiremezsiniz.

    Altyordamlar için daha iyi bir Uygulamaya sahip olmak için: Bir STACK'a ihtiyacınız var

  12. Yığın : Hafıza alanını bir "yığın" olarak çalışmak üzere tanımlarsınız, yığındaki değerleri "itebilir" ve aynı zamanda son "itilen" değeri "patlatabilirsiniz". Bir yığını uygulamak için, yığının gerçek “başını” gösteren bir Yığın İşaretçisi (Talimat İşaretçisi'ne benzer) gerekir . Bir değeri “ittiğiniz” zaman, yığın işaretçisi azalır ve değeri saklarsınız. “Açtığınızda”, değeri gerçek Yığın İşaretçisi'nde alırsınız ve ardından Yığın İşaretçisi artırılır.

  13. Altyordamlar Şimdi bir olduğunu yığını biz doğru altyordamlarını uygulayabilir iç içe çağrılar oluşturulması . Uygulama benzerdir, ancak Yönerge İşaretçisini önceden tanımlanmış bir bellek konumunda saklamak yerine, yığındaki IP değerini "iter" . Altyordamın sonunda, sadece yığındaki değeri “pop” hale getiriyoruz ve orijinal çağrıdan sonra talimatlara etkili bir şekilde geri dönüyoruz . Bir "yığın" a sahip olan bu uygulama, başka bir altyordamdan altyordam çağrılmasına izin verir. Bu uygulama ile , temel talimatları veya diğer altyordamları yapı taşları olarak kullanarak yeni talimatları altyordam olarak tanımlarken çeşitli soyutlamalar oluşturabiliriz .

  14. Özyineleme : Bir altyordam kendini çağırdığında ne olur? Buna "özyineleme" denir.

    Sorun: Yerel ara sonuçların üzerine yazmak, bir alt yordamın belleğe depolayabiliyor olması. Aradığınız yana /, aynı adımları yeniden eğer ara sonuç önceden tanımlanmış hafıza konumları (küresel değişkenler) saklanır onlar iç içe çağrılarda üzerine yazılır olacaktır.

    Çözüm: tekrarlama, alt programlar yerel ara sonuçları depolamak gerektiği izin vermek için yığın içinde her biri üzerinde, bu nedenle, yinelemeli çağrı ara sonuçlar, farklı bellek konumlarında saklanır (doğrudan veya dolaylı olarak).

...

özyinelemeye ulaştıktan sonra burada duruyoruz.

Sonuç:

Bir Neumann mimarisinin yılında açıkça "Yineleme" den daha basit / temel kavramdır “Özyineleme" . Biz bir form var 'İterasyon' 7. seviye iken 'Özyineleme' kavramları hiyerarşi düzeyine 14 altındadır.

Yineleme , makine kodunda her zaman daha hızlı olacaktır, çünkü daha az talimat anlamına gelir, bu nedenle daha az CPU döngüsü.

Hangisi daha iyi"?

  • Basit, sıralı veri yapılarını işlerken "yineleme" yi kullanmalısınız ve her yerde "basit döngü" yapılacaktır.

  • Özyinelemeli bir veri yapısını işlemeniz gerektiğinde (özyinelemeyi "Fraktal Veri Yapıları" olarak adlandırmak istiyorum) veya özyinelemeli çözüm açıkça "zarif" olduğunda "özyineleme" yi kullanmalısınız.

Tavsiye : iş için en iyi aracı kullanın, ancak akıllıca seçim yapmak için her bir aracın iç işleyişini anlayın.

Son olarak, özyineleme kullanmak için birçok fırsatınız olduğunu unutmayın. Her yerde Özyinelemeli Veri Yapılarınız var, şimdi bir tanesine bakıyorsunuz: DOM'un okuduklarınızı destekleyen kısımları bir RDS, bir JSON ifadesi bir RDS, bilgisayarınızdaki hiyerarşik dosya sistemi bir RDS, yani: dosyalar ve dizinler içeren bir kök dizin, dosyalar ve dizinler içeren her dizin, dosyalar ve dizinler içeren dizinlerin her biri ...


2
İlerlemenizin 1) gerekli olduğunu ve 2) bunun durduğunu varsayıyorsunuz. Ancak 1) gerekli değildir (örneğin, kabul edilen cevap açıklandığı gibi özyineleme bir sıçramaya dönüştürülebilir, bu nedenle yığın gerekmez) ve 2) orada durması gerekmez (örneğin, sonunda 2. adımda tanıttığınız gibi değişebilir bir duruma sahipseniz kilitlere ihtiyaç duyan eşzamanlı işleme ulaşacaksınız, bu yüzden her şey yavaşlıyor; işlevsel / özyinelemeli gibi değişmez bir çözüm kilitlenmeyi önleyecektir, bu yüzden daha hızlı / daha paralel olabilir) .
hmijail resignees

2
"özyineleme bir sıçramaya dönüştürülebilir" yanlıştır. Gerçekten faydalı özyineleme bir sıçramaya dönüşemez. Kuyruk çağrısı "özyineleme" derleyici tarafından bir döngüye basitleştirilebilecek bir şeyi "özyineleme olarak" kodladığınız özel bir durumdur. Ayrıca "değişmez" i "özyineleme" ile karıştırıyorsunuz, bunlar dik kavramlar.
Lucio M. Tato

"Gerçekten yararlı özyineleme bir sıçrama haline dönüştürülemez" -> böylece kuyruk çağrı optimizasyonu bir şekilde işe yaramaz? Ayrıca, değişmez ve özyineleme dik olabilir, ancak döngüyü değişebilir sayaçlarla bağlarsınız - 9. adımınıza bakın. Döngü ve özyinelemenin radikal olarak farklı kavramlar olduğunu düşündüğünüzü düşünüyorum; değiller. stackoverflow.com/questions/2651112/…
hmijail resignees

@hmijail "Yararlı" kelimeden daha iyi bir kelimenin "doğru" olduğunu düşünüyorum. Kuyruk özyineleme gerçek özyineleme değildir, çünkü yalnızca koşulsuz dallanmayı gizlemek için işlev çağrısı sözdizimini kullanır, yani yineleme. Gerçek özyineleme bize geri izleme yığını sağlar. Bununla birlikte, kuyruk özyineleme hala ifade edicidir, bu da onu faydalı kılar. Kodun doğruluk açısından analiz edilmesini kolaylaştıran veya kolaylaştıran özyineleme özellikleri, kuyruk çağrıları kullanılarak ifade edildiğinde yinelemeli koda verilir. Rağmen bazen ekstra parametreler gibi kuyruk versiyonunda ekstra komplikasyon ile biraz dengelenir.
Kaz

34

Alternatifin, bir sıralamada bahsettiğiniz sıralama veya ikili ağaç algoritmalarında olduğu gibi bir yığını açıkça yönetmek olduğu durumlarda özyineleme daha hızlı olabilir.

Java'da özyinelemeli bir algoritmayı yeniden yazmanın yavaşlattığı bir durum yaşadım.

Bu nedenle doğru yaklaşım ilk önce en doğal şekilde yazmak, yalnızca profil oluşturma kritik olduğunu gösteriyorsa optimize edin ve sonra sözde gelişmeyi ölçün.


2
+ 1 " ilk önce en doğal şekilde yazın " ve özellikle " yalnızca profil oluşturma kritik olduğunu gösteriyorsa optimize edin "
TripeHound

2
Donanım yığının el ile uygulanan, yığın içi bir yazılımdan daha hızlı olabileceğini onaylamak için +1. Etkili bir şekilde "hayır" yanıtlarının yanlış olduğunu gösterir.
sh1


12

Her biri, yineleme ve özyineleme için kesinlikle ne yapılması gerektiğini düşünün.

  • yineleme: döngü başlangıcına atlama
  • özyineleme: çağrılan işlevin başlangıcına atlama

Burada farklılıklar için fazla yer olmadığını görüyorsunuz.

(Özyinelemenin bir kuyruk çağrısı ve derleyicinin bu optimizasyondan haberdar olduğunu varsayıyorum)


9

Buradaki cevapların çoğu, özyinelemenin neden yinelemeli çözümlerden daha yavaş olduğu açık suçluyu unutmaktadır. Yığın çerçevelerinin birikmesi ve yırtılması ile bağlantılıdır, ancak tam olarak bu değildir. Genellikle her bir özyineleme için otomatik değişkenin depolanmasında büyük bir fark vardır. Döngü içeren yinelemeli bir algoritmada, değişkenler genellikle kayıtlarda tutulur ve dökülseler bile Seviye 1 önbelleğinde bulunurlar. Özyinelemeli bir algoritmada, değişkenin tüm aracı durumları yığın üzerinde depolanır, yani belleğe çok daha fazla dökülme meydana getirirler. Bu, aynı miktarda işlem yapsa bile, sıcak döngüde çok fazla bellek erişimine sahip olacağı ve onu daha da kötüleştireceği anlamına gelir, bu bellek işlemleri önbellekleri daha az etkili hale getiren berbat bir yeniden kullanım oranına sahiptir.

TL; DR özyinelemeli algoritmalar genellikle yinelemeli olanlardan daha kötü bir önbellek davranışına sahiptir.


6

Buradaki cevapların çoğu yanlış . Doğru cevap duruma bağlı . Örneğin, burada bir ağaçta yürüyen iki C işlevi vardır. İlk olarak özyinelemeli olan:

static
void mm_scan_black(mm_rc *m, ptr p) {
    SET_COL(p, COL_BLACK);
    P_FOR_EACH_CHILD(p, {
        INC_RC(p_child);
        if (GET_COL(p_child) != COL_BLACK) {
            mm_scan_black(m, p_child);
        }
    });
}

Ve burada yineleme kullanılarak uygulanan aynı işlev var:

static
void mm_scan_black(mm_rc *m, ptr p) {
    stack *st = m->black_stack;
    SET_COL(p, COL_BLACK);
    st_push(st, p);
    while (st->used != 0) {
        p = st_pop(st);
        P_FOR_EACH_CHILD(p, {
            INC_RC(p_child);
            if (GET_COL(p_child) != COL_BLACK) {
                SET_COL(p_child, COL_BLACK);
                st_push(st, p_child);
            }
        });
    }
}

Kodun ayrıntılarını anlamak önemli değildir. Sadece bu pdüğümler ve P_FOR_EACH_CHILDyürüyüş yapıyor. Yinelemeli versiyonda st, düğümlerin üzerine itildiği ve sonra patlatıldığı ve manipüle edildiği açık bir desteğe ihtiyacımız var .

Özyinelemeli işlev yinelemeden çok daha hızlı çalışır. Bunun nedeni, ikincisinde, her bir öğe CALLiçin, işleve st_pushbir ve sonra başka bir öğeye ihtiyaç duyulmasıdır st_pop.

İlkinde, yalnızca CALLher düğüm için özyinelemeye sahipsiniz .

Ayrıca, çağrı kaydındaki değişkenlere erişmek inanılmaz derecede hızlıdır. Bu, her zaman en iç önbellekte olması muhtemel olan bellekten okuduğunuz anlamına gelir. Öte yandan, açık bir yığın, aşağıdakilerle desteklenmelidir malloc: öbekten erişilmesi çok daha yavaş olan bellek.

Böyle inlining dikkatli optimizasyonu ile st_pushve st_popben özyinelemeli yaklaşımla kabaca paritesine ulaşabilir. Ama en azından bilgisayarımda, yığın belleğe erişim maliyeti özyinelemeli çağrının maliyetinden daha büyük.

Ancak bu tartışma çoğunlukla tartışmalıdır çünkü özyinelemeli ağaç yürüyüşü yanlıştır . Yeterince büyük bir ağacınız varsa, çağrı dizisi alanınız bitecek ve bu nedenle yinelemeli bir algoritma kullanılmalıdır.


Benzer bir durumla karşılaştığımı ve özyinelemenin yığındaki bir el yığınından daha hızlı olabileceği durumlar olduğunu doğrulayabilirim. Özellikle bir işlevi çağırmanın bazı ek yüklerinden kaçınmak için derleyicide optimizasyon açıldığında.
while1fork

1
7 düğüm ikili ağacın 10 ^ 8 kez ön siparişi verilmiş. Özyineleme 25ns. Açık yığın (bağlı veya işaretli değil - çok fazla fark yaratmaz) ~ 15ns. Özyineleme, yalnızca itme ve atlamanın yanı sıra daha fazlasını (kayıt kaydetme ve geri yükleme + (genellikle) daha katı çerçeve hizalamaları) yapmalıdır. (Ve dinamik olarak bağlı kütüphanelerde PLT ile daha da kötüleşir.) Açık yığını yığın ayırmanıza gerek yoktur. İlk karesi normal çağrı yığınında olan bir engel yapabilirsiniz, böylece ilk bloğu aşmadığınız en yaygın durum için önbellek yerini feda etmeyebilirsiniz.
PSkocik

3

Genel olarak, hayır, her iki formda da uygulanabilir uygulamaları olan herhangi bir gerçekçi kullanımda özyineleme bir döngüden daha hızlı olmayacaktır. Demek istediğim, sonsuza kadar süren döngüleri kodlayabilirsiniz, ancak aynı döngüyü uygulamanın özyineleme yoluyla herhangi bir uygulamasından daha iyi performans gösterebilecek daha iyi yollar olurdu.

Nedeni ile ilgili olarak kafasına çiviyi vurdun; yığın kareleri oluşturmak ve yok etmek basit bir sıçramadan daha pahalıdır.

Ancak, "her iki formda da uygulanabilir uygulamalara sahip" dedim. Birçok sıralama algoritması gibi şeyler için, sürecin doğal bir parçası olan çocuk "görevlerinin" ortaya çıkması nedeniyle, bir yığının kendi versiyonunu etkili bir şekilde ayarlamayan bunları uygulamanın çok uygun bir yolu yoktur. Böylece özyineleme, algoritmayı döngü yoluyla uygulamaya çalışmak kadar hızlı olabilir.

Düzenleme: Bu yanıt, çoğu temel veri türünün değiştirilebilir olduğu işlevsel olmayan diller olduğunu varsayar. İşlevsel diller için geçerli değildir.


Bu nedenle, birkaç özyineleme vakası, özyinelemenin sıkça kullanıldığı dillerde derleyiciler tarafından sıklıkla optimize edilir. Örneğin, F # 'da, .tail opcode ile kuyruk yinelemeli işlevlere tam desteğe ek olarak, genellikle döngü olarak derlenmiş yinelemeli bir işlev görürsünüz.
em70

Evet. Kuyruk özyineleme bazen her iki dünyanın da en iyisi olabilir - özyinelemeli bir görevi yerine getirmenin işlevsel olarak "uygun" yolu ve bir döngü kullanma performansı.
Amber

1
Bu genel olarak doğru değil. Bazı ortamlarda, mutasyon (GC ile etkileşime giren) kuyruk özyinelemesinden daha pahalıdır, bu da çıkışta ekstra yığın çerçevesi kullanmayan daha basit bir döngüye dönüşür.
Dietrich Epp

2

Herhangi bir gerçekçi sistemde, hayır, bir yığın çerçevesi oluşturmak her zaman bir INC ve JMP'den daha pahalı olacaktır. Bu yüzden gerçekten iyi derleyiciler otomatik olarak kuyruk özyinelemesini aynı çerçeveye, yani tepegöz olmadan bir çağrıya dönüştürür, böylece daha okunabilir kaynak sürümünü ve daha verimli derlenmiş sürümü elde edersiniz. Bir gerçekten, gerçekten iyi derleyici bile mümkün olduğu kuyruk özyinelemenin içine normale özyinelemeye dönüştürmek mümkün olmalıdır.


1

Fonksiyonel programlama, " nasıl " yerine " ne " ile ilgilidir.

Dil uygulayıcıları, kodun olması gerekenden daha optimize edilmesini sağlamazsak, kodun altında nasıl çalıştığını optimize etmenin bir yolunu bulur. Özyineleme, kuyruk çağrısı optimizasyonunu destekleyen dillerde de optimize edilebilir.

Programcı açısından daha önemli olan, en başta optimizasyondan ziyade okunabilirlik ve sürdürülebilirliktir. Yine, "erken optimizasyon tüm kötülüklerin köküdür".


0

Bu bir tahmin. Genellikle, her ikisi de gerçekten iyi algoritmalar (uygulama zorluğunu saymaz) kullanıyorsa, özyineleme muhtemelen iyi boyutta problemlerde sık sık veya hiç döngü yapmaz, kuyruk çağrısı özyinelemeli bir dil (ve kuyruk özyinelemeli algoritma ile) kullanılması farklı olabilir ve döngülerle de dilin bir parçası olarak) - muhtemelen çok benzer olan ve hatta bazen tekrarlamayı tercih eden.


0

Teoriye göre aynı şeyler. Aynı O () karmaşıklığına sahip özyineleme ve döngü aynı teorik hızda çalışacaktır, ancak elbette gerçek hız dile, derleyiciye ve işlemciye bağlıdır. Sayının gücüne sahip örnek, O (ln (n)) ile yineleme yoluyla kodlanabilir:

  int power(int t, int k) {
  int res = 1;
  while (k) {
    if (k & 1) res *= t;
    t *= t;
    k >>= 1;
  }
  return res;
  }

1
Big O “ile orantılıdır”. Her iki yüzden vardır O(n), ancak bir olabilir almak xherkes için, diğerinden daha uzun süreleri n.
ctrl-alt-delor
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.