Derleyiciler özyinelemeli mantığı eşdeğer özyinelemesiz mantığa dönüştürebilir mi?


15

F # öğreniyorum ve C # programlarken nasıl düşündüğümü etkilemeye başlıyor. Bu amaçla, sonucun okunabilirliği geliştirdiğini hissettiğimde özyineleme kullanıyorum ve bunun bir yığın taşmasına dönüşmesini öngöremiyorum.

Bu, derleyicilerin özyinelemeli işlevleri eşdeğer özyinelemeli olmayan bir forma otomatik olarak dönüştürüp dönüştüremeyeceğini sormama neden oluyor?


kuyruk çağrı optimizasyonu temel bir örnek ise iyi ama sadece return recursecall(args);özyineleme için varsa , daha karmaşık şeyler açık bir yığın oluşturarak ve aşağı sarma ile mümkündür, ama onlar şüpheliyim
cırcır ucube

@ratchet ucube: Özyineleme "yığın kullanan hesaplama" anlamına gelmez.
Giorgio

1
@Giorgio Biliyorum ama bir yığın özyinelemeyi bir döngüye dönüştürmenin en kolay yoludur
cırcır ucube

Yanıtlar:


21

Evet, bazı diller ve derleyiciler yinelemeli mantığı yinelemesiz mantığa dönüştürür. Bu, kuyruk çağrısı optimizasyonu olarak bilinir - tüm yinelemeli çağrıların kuyruk çağrısı optimizasyonu olmadığını unutmayın. Bu durumda, derleyici formun bir işlevini tanır:

int foo(n) {
  ...
  return bar(n);
}

Burada, dil, döndürülen sonucun başka bir işlevden kaynaklandığını tanıyabilir ve yeni bir yığın çerçeveli bir işlev çağrısını atlamaya dönüştürebilir.

Klasik faktöryel yöntemin farkına varın:

int factorial(n) {
  if(n == 0) return 1;
  if(n == 1) return 1;
  return n * factorial(n - 1);
}

olduğu değil çünkü dönüş gerekli denetim kuyruk çağrı optimizatable.

Bu kuyruk çağrısını optimize edilebilir hale getirmek için,

int _fact(int n, int acc) {
    if(n == 1) return acc;
    return _fact(n - 1, acc * n);
}

int factorial(int n) {
    if(n == 0) return 1;
    return _fact(n, 1);
}

Bu kodu gcc -O2 -S fact.cderlemek (derleyicide optimizasyonu etkinleştirmek için -O2 gereklidir, ancak -O3'ün daha fazla optimizasyonu ile bir insanın okuması zorlaşır ...)

_fact:
.LFB0:
        .cfi_startproc
        cmpl    $1, %edi
        movl    %esi, %eax
        je      .L2
        .p2align 4,,10
        .p2align 3
.L4:
        imull   %edi, %eax
        subl    $1, %edi
        cmpl    $1, %edi
        jne     .L4
.L2:
        rep
        ret
        .cfi_endproc

Bir segment görebilir .L4, jneyerine göre call(yeni bir yığın çerçeveye sahip olan bir alt yordam çağrı yapar).

Bunun C ile yapıldığını lütfen unutmayın. Java'da kuyruk çağrı optimizasyonu zordur ve JVM uygulamasına bağlıdır - tail-recursion + java ve tail-recursion + optimizasyonu göz atmak için iyi etiket kümeleridir. Diğer JVM dilleri optimize kuyruk özyineleme daha iyi (gerektirir denemek Clojure (edebiliyoruz bulabilir tekrarlanmasını kuyruk çağrı optimize etmek için) veya scala).


1
OP'nin sorduğu şeyden emin değilim. Çalışma zamanının yığın alanını belirli bir şekilde kullanmaması veya tüketmemesi, işlevin özyinelemediği anlamına gelmez.

1
@MattFenwick Ne demek istiyorsun? "Bu beni derleyicilerin özyinelemeli fonksiyonları eşdeğer özyinelemeli olmayan bir biçime otomatik olarak dönüştürüp dönüştüremeyeceğini sormaya yönlendirir" - yanıt "belirli koşullar altında evet" tir. Koşullar gösterilmiştir ve bahsettiğim kuyruk çağrı optimizasyonları ile bazı diğer popüler dillerde bazı gotcha'lar var.

9

Hareketlerine dikkat et.

Cevap evet, ama her zaman değil, hepsi değil. Bu, birkaç farklı isimle giden bir tekniktir, ancak burada ve wikipedia'da oldukça kesin bilgiler bulabilirsiniz .

Ben "Kuyruk Çağrı Optimizasyonu" adını tercih ama diğerleri var ve bazı insanlar terimi karıştıracaktır.

Bu, gerçekleştirilmesi gereken birkaç önemli şey olduğunu söyledi:

  • Kuyruk çağrısını optimize etmek için, kuyruk çağrısı çağrının yapıldığı sırada bilinen parametreleri gerektirir. Parametrelerden biri işlevin kendisine bir çağrıdır araç ise, o zaman bu olamaz bu derleme sırasında genişletilmiş edilemez söyledi döngünün keyfi iç içe gerektirecektir, çünkü bir döngü çevrilecektir.

  • C # does not güvenilir optimize kuyruk aramaları. IL, F # derleyicisinin yayınlayacağı talimatı verir, ancak C # derleyicisi tutarsız bir şekilde yayar ve JIT durumuna bağlı olarak, JIT bunu yapabilir ya da yapmayabilir. Tüm endikasyonlar, C # 'da optimize edilen kuyruk çağrılarınıza güvenmemelisiniz, bunu yaparken taşma riski önemli ve gerçek


1
OP'nin sorduğu şeyin bu olduğundan emin misiniz? Diğer cevap altında yayınladığım gibi, çalışma zamanının yığın alanını belirli bir şekilde kullanmaması ya da tüketmemesi, işlevin özyinelemediği anlamına gelmez.

1
@MattFenwick aslında harika bir nokta, buna bağlı olarak gerçek anlamda, kuyruk çağrı talimatlarını yayan F # derleyicisi özyinelemeli mantığı tamamen koruyor, sadece JIT'e bunu yığın-alan yerine yığın-alan-değiştiren bir tarzda yürütmesi talimatını veriyor- ancak, diğer derleyiciler tam anlamıyla bir döngüye derlenebilir. (Teknik olarak, JIT bir döngü için toplamda bir döngü veya muhtemelen döngüsiz bir moda derliyor)
Jimmy Hoffa
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.