TCO olmadığında, yığını üfleme konusunda ne zaman endişelenmelisiniz?


14

Her seferinde JVM'yi hedefleyen yeni bir programlama dili hakkında bir tartışma var, kaçınılmaz olarak insanlar şöyle şeyler söylüyor:

"JVM kuyruk arama optimizasyonunu desteklemediğinden, çok sayıda patlayan yığın öngörüyorum"

Bu tema üzerinde binlerce varyasyon var.

Şimdi biliyorum ki, örneğin Clojure gibi bazı dillerde kullanabileceğiniz özel bir tekrar yapısı vardır.

Anlamadığım şey: Kuyruk çağrısı optimizasyonunun eksikliği ne kadar ciddi? Ne zaman endişelenmeliyim?

Ana karışıklık kaynağım muhtemelen Java'nın şimdiye kadarki en başarılı dillerden biri olması ve JVM dillerinin birkaçının oldukça iyi performans gösterdiği gerçeğinden kaynaklanıyor. Bu nasıl TCO olmaması gerçekten eğer mümkün herhangi bir endişe?


4
TCO olmadan yığını üfleyecek kadar derin özyinelemeniz varsa, o zaman TCO ile bile bir sorun yaşarsınız
cırcır ucube

18
@ratchet_freak Bu saçmalık. Şemada döngüler bile yoktur, ancak spesifikasyon TCO desteğini zorunlu kıldığı için, büyük bir dart seti üzerinde özyinelemeli yineleme, zorunlu bir döngüden daha pahalı değildir (Şema yapısının bir değer döndürdüğü bonusu ile).
itsbruce

6
@ratchetfreak TCO belirli bir yolla yazılan özyinelemeli işlevlerin (yani kuyruk özyinelemeli) isteseler bile yığını üfleyememelerini sağlayan bir mekanizmadır. İfadeniz yalnızca özyinelemeli olarak yazılmayan özyineleme için anlamlıdır, bu durumda haklısınız ve TCO size yardımcı olmaz.
Evicatos

2
Son baktım, 80x86 da (yerli) kuyruk çağrı optimizasyonu yapmıyor. Ancak bu, dil geliştiricilerin onu kullanan dilleri taşımasını engellemedi. Derleyici bir jsr'a karşı ne zaman bir sıçrama kullanabileceğini ve herkesin mutlu olduğunu tanımlar. Aynı şeyi bir JVM'de de yapabilirsiniz.
kdgregory

3
@kdgregory: Ancak x86'da GOTOJVM yok. Ve x86 birlikte çalışma platformu olarak kullanılmaz. JVM'de yoktur GOTOve Java Platformunu seçmenin ana nedenlerinden biri birlikte çalışmadır . TCO'yu JVM'ye uygulamak istiyorsanız , yığına bir şey yapmanız gerekir . Kendiniz yönetin (yani JVM çağrı yığınını hiç kullanmayın), trambolin kullanın GOTO, bunun gibi istisnalar kullanın . Tüm bu durumlarda, JVM çağrı yığını ile uyumsuz hale gelirsiniz. Java ile yığın uyumlu olmak, TCO'ya ve yüksek performansa sahip olmak imkansızdır. Bu üç kişiden birini feda etmelisin.
Jörg W Mittag

Yanıtlar:


16

Bunu düşünün, diyelim ki Java'daki tüm döngülerden kurtulduk (derleyici yazarları grevde falan). Şimdi faktöriyel yazmak istiyoruz, bu yüzden böyle bir şeyi düzeltebiliriz

int factorial(int i){ return factorial(i, 1);}
int factorial(int i, int accum){
  if(i == 0) return accum;
  return factorial(i-1, accum * i);
}

Şimdi oldukça zeki hissediyoruz, çarpanlarımızı döngüsüz bile yazmayı başardık! Ancak test ettiğimizde, makul boyutta herhangi bir sayı ile, toplam sahip olma maliyeti olmadığından yığın akışı hataları aldığımızı fark ediyoruz.

Gerçek Java'da bu bir sorun değildir. Bir kuyruk özyinelemeli algoritmamız varsa, onu bir döngüye dönüştürebilir ve iyi olabiliriz. Ancak, döngüsüz diller ne olacak? O zaman yeni evlendin. Bu yüzden clojure bu recurforma sahiptir, onsuz, tamamlanmamış bile değildir (Sonsuz döngüler yapmanın yolu yoktur).

JVM, Frege, Kawa (Şema), Clojure'u hedefleyen fonksiyonel dil sınıfı her zaman kuyruk çağrılarının eksikliğiyle uğraşmaya çalışmaktadır, çünkü bu dillerde TC döngüler yapmanın deyimsel yoludur! Şemaya çevrilirse, yukarıdaki faktöriyel iyi bir faktöriyel olacaktır. 5000 kez döngü yapmak programınızın çökmesine neden olsaydı çok rahatsız edici olurdu. Bu, recurözel formlar, kendi kendine çağrıları optimize etme, tramplen yapma, ne olursa olsun ek açıklamalar ile çözülebilir . Ancak hepsi ya performans vuruşlarını ya da programcı üzerinde gereksiz çalışmaları zorlar.

Artık Java da serbest kalmıyor, çünkü TCO'da daha sonra özyineleme var, karşılıklı özyinelemeli işlevler ne olacak? Basitçe döngülere çevrilemezler, ancak JVM tarafından hala optimize edilmezler. Bu, Java kullanarak karşılıklı özyineleme kullanarak algoritmalar yazmaya çalışmayı oldukça tatsız kılar, çünkü iyi bir performans / aralık istiyorsanız, döngülere sığdırmak için kara büyü yapmanız gerekir.

Yani, özet olarak, bu birçok vaka için büyük bir anlaşma değil. Çoğu kuyruk çağrısı ya sadece bir yığın karenin derinliklerinde ilerler.

return foo(bar, baz); // foo is just a simple method

veya özyineleme. Ancak, buna uymayan TC sınıfı için her JVM dili acıyı hisseder.

Ancak henüz TCO'muzun olmamasının iyi bir nedeni var. JVM bize yığın izleri veriyor. TCO ile "mahkum" olduğunu bildiğimiz yığın çerçevelerini sistematik olarak ortadan kaldırıyoruz, ancak JVM bunları daha sonra bir yığın izlemesi için isteyebilir! Diyelim ki böyle bir FSM uyguluyoruz, her eyalet bir sonrakini çağırıyor. Önceki durumların tüm kayıtlarını sileriz, böylece bir iz bize hangi eyaleti gösterir, ancak oraya nasıl geldiğimizle ilgili hiçbir şey göstermez.

Ek olarak ve daha acil olarak, bayt kodu doğrulamanın büyük kısmı yığın tabanlıdır ve bayt kodunu doğru bir olasılık olmadığını doğrulamamıza izin veren şeyi ortadan kaldırır. Bu ve Java'nın döngülere sahip olması arasında, TCO, JVM mühendislerine göre biraz daha fazla sorun gibi görünüyor.


2
En büyük sorun, tamamen yığın denetimine dayanan bayt kodu doğrulayıcıdır. Bu JVM spesifikasyonunda büyük bir hata. 25 yıl önce, JVM tasarlandığında, insanlar zaten JVM bayt kod dilinin, o dilin güvensiz olmasını sağlamaktan ve daha sonra gerçekte bayt kodu doğrulamasına güvenmek yerine, ilk etapta güvenli olmasının daha iyi olacağını söyledi. Bununla birlikte, Matthias Felleisen (Şema topluluğunun önde gelen isimlerinden biri), bayt kodu doğrulayıcısını korurken JVM'ye kuyruk çağrılarının nasıl eklenebileceğini gösteren bir makale yazdı.
Jörg W Mittag

2
İlginçtir, IBM tarafından J9 JVM gelmez TCO'nuzu gerçekleştirin.
Jörg W Mittag

1
@jozefg İlginçtir ki, hiç kimse döngüler için yığın izleme girişlerini umursamaz, bu nedenle yığın izleme argümanı en azından kuyruk özyinelemeli işlevler için su tutmaz.
Ingo

2
@MasonWheeler Bu tam olarak benim açımdan: stacktrace size hangi yinelemenin gerçekleştiğini söylemiyor. Bunu sadece dolaylı olarak, döngü değişkenlerini, vb. Sadece sonuncusu ilginç! Ve döngülerde olduğu gibi, yerel değişkenleri, argüman değerlerini vb. İnceleyerek hangi özyineleme olduğunu belirleyebilirsiniz
Ingo

3
@Ingo: Bir işlev yalnızca kendi kendine geri çekiliyorsa yığın izlemesi fazla görünmeyebilir. Bununla birlikte, bir grup fonksiyon mutal olarak özyinelemeli ise, bir yığın izlemesi bazen çok fazla şey gösterebilir.
supercat

7

Kuyruk çağrıları optimizasyonu esas olarak kuyruk özyineleme nedeniyle önemlidir. Bununla birlikte, JVM'nin kuyruk çağrılarını optimize etmemesinin gerçekten iyi olduğu konusunda bir argüman vardır: TCO yığının bir kısmını yeniden kullandığından, bir istisnanın yığın izlemesi eksik olacak, böylece hata ayıklamayı biraz daha zorlaştıracaktır.

JVM'nin sınırlamaları üzerinde çalışmanın yolları vardır:

  1. Basit kuyruk özyineleme derleyici tarafından bir döngü için optimize edilebilir.
  2. Program devam eden bir tarzdaysa, “tramplen” kullanmak önemsizdir. Burada, bir işlev nihai sonucu döndürmez, daha sonra dışarıdan yürütülür. Bu teknik, bir derleyici yazarının rastgele karmaşık kontrol akışını modellemesine izin verir.

Bunun daha büyük bir örneğe ihtiyacı olabilir. Kapakları olan bir dil düşünün (ör. JavaScript veya benzeri). Faktöriyeli olarak yazabiliriz

def fac(n, acc = 1) = if (n <= 1) acc else n * fac(n-1, acc*n)

print fac(x)

Şimdi bunun yerine geri arama yapabiliriz:

def fac(n, acc = 1) =
  if (n <= 1) acc
  else        (() => fac(n-1, acc*n))  // this isn't full CPS, but you get the idea…

var continuation = (() => fac(x))
while (continuation instanceof function) {
  continuation = continuation()
}
var result = continuation
print result

Bu artık sürekli yığın alanında çalışıyor, bu da yine de kuyruk yinelemeli olduğu için biraz saçma. Bununla birlikte, bu teknik tüm kuyruk çağrılarını sabit yığın alanına düzleştirebilir . Ve eğer program CPS'de ise, bu durum çağrı kaydının genel olarak sabit olduğu anlamına gelir (CPS'de her çağrı bir kuyruk çağrısıdır).

Bu tekniğin önemli bir dezavantajı, hata ayıklamanın çok daha zor, uygulanması biraz daha zor ve daha az performans - kullandığım tüm kapanışları ve dolaylamaları görmeniz.

Bu nedenlerden dolayı, VM'nin kuyruk çağrılarını desteklememek için iyi nedenleri olan Java gibi bir kuyruk çağrısı op - dilleri kullanması büyük ölçüde tercih edilir.


1
"TCO yığının bir bölümünü yeniden kullandığından, bir istisnanın yığın izlemesi tamamlanmayacak" - evet, ancak daha sonra, bir döngü içindeki yığın yığını da eksik - döngünün ne sıklıkta yürütüldüğünü kaydetmiyor. - Ne yazık ki, JVM uygun kuyruk çağrılarını desteklese bile, hata ayıklama sırasında hala biri devre dışı kalabilir. Ve sonra, üretim için, TCO'nun kodun 100.000 veya 100.000.000 kuyruk çağrısı ile çalıştığından emin olmasını sağlayın.
Ingo

1
(1) Döngüler özyineleme olarak uygulanmadığında, yığın üzerinde görünmeleri için herhangi bir gerekçe yoktur (kuyruk çağrısı ≠ atlama ≠ çağrısı). (2) TCO, kuyruk özyineleme optimizasyonundan daha geneldir. Cevabım örnek olarak özyineleme kullanıyor . (3) TCO'ya dayanan bir tarzda programlıyorsanız, bu optimizasyonu kapatmak bir seçenek değildir - tam TCO veya tam yığın izleri bir dil özelliğidir veya değildir. Örneğin Scheme, TCO dezavantajlarını daha gelişmiş bir istisna sistemiyle dengelemeyi başarır.
amon

1
(1) tamamen katılıyorum. Ancak aynı mantıkla, return foo(....);yöntem foo(2) 'de elbette tamamen anlaşılan yüzlerce ve binlerce yığın izleme girişini tutmanın hiçbir mantığı yoktur . Yine de, döngülerden, atamalardan (!), Deyim dizilerinden eksik izlemeyi kabul ediyoruz. Örneğin, bir değişkende beklenmedik bir değer bulursanız, mutlaka oraya nasıl geldiğini bilmek istersiniz. Ama bu durumda eksik izlerden şikayet etmiyorsunuz. Bir şekilde beynimize kazınmış olduğu için a) sadece çağrılarda olur b) tüm çağrılarda olur. Her ikisi de bir anlam ifade etmiyor, IMHO.
Ingo

(3) Katılmıyorum. Normal yığın ile kurtulmak için bazı küçük N için, kod boyutu N boyutu ile hata ayıklamak imkansız olması için hiçbir neden göremiyorum. Ve sonra, düğmeyi açmak ve TCO'yu açmak için - prob boyutu üzerindeki kısıtlamayı etkili bir şekilde düşürmek.
Ingo

@Ingo “Katılmıyorum. Normal yığından kurtulmak için yeterince küçük bazı N için kodumu N boyutunda bir sorunla hata ayıklamanın imkansız olması için hiçbir neden göremiyorum. ” TCO / TCE bir CPS dönüşümü içinse, kapatıldığında yığın taşacak ve program çökecektir, bu nedenle hata ayıklama mümkün olmayacaktır. Google, tesadüfen ortaya çıkan bu sorun nedeniyle TCO'yu V8 JS'de uygulamayı reddetti . Programcının gerçekten TCO ve yığın izinin kaybını istediğini beyan edebilmesi için bazı özel sözdizimleri isteyeceklerdi. TCO tarafından istisnaların da engellenip engellenmediğini bilen var mı?
Shelby Moore III

6

Bir programdaki çağrıların önemli bir kısmı kuyruk çağrılarıdır. Her altyordamın son bir çağrısı vardır, bu nedenle her altyordamın en az bir kuyruk çağrısı vardır. Kuyruk çağrıları GOTO, bir altyordam çağrısının güvenliği ancak performans özelliklerine sahiptir .

Doğru Kuyruk Çağrılarına sahip olmak, başka türlü yazamayacağınız programları yazmanıza olanak tanır. Örneğin, bir durum makinesini ele alalım. Bir durum makinesi, her durumun bir alt rutin olması ve her bir durum geçişinin bir alt rutin çağrısı olmasıyla çok doğrudan uygulanabilir. Bu durumda, aramadan sonra arama yaptıktan sonra eyaletten eyalete geçiş yaparsınız ve aslında asla geri dönmezsiniz! Doğru Kuyruk Çağrıları olmadan, hemen yığını havaya uçururdunuz.

PTC olmadan, GOTOkontrol akışı veya bunun gibi bir şey olarak Trambolin veya istisnaları kullanmanız gerekir . Bu çok daha çirkin ve devlet makinesinin doğrudan 1: 1 temsili değil.

(I akıllıca "döngü" Örnek sıkıcı kullanılarak önlenir ne not edin. Bu PTC da dile yararlıdır bir örnek olan döngü).

TCO yerine kasıtlı olarak "Doğru Kuyruk Çağrıları" terimini kullandım. TCO bir derleyici optimizasyonudur. PTC, her derleyicinin TCO yapmasını gerektiren bir dil özelliğidir .


The vast majority of calls in a program are tail calls. Çağrılan yöntemlerin "büyük çoğunluğu" kendi çağrılarından daha fazlasını yaparsa değil. Every subroutine has a last call, so every subroutine has at least one tail call. Bu yanlış olarak trivially gösterilebilir: return a + b. (Tabii ki, temel aritmetik işlemlerin işlev çağrıları olarak tanımlandığı deli bir dilde değilseniz.)
Mason Wheeler

1
Msgstr "İki sayı eklemek iki sayı eklemektir." Olmadığı diller hariç. Tek bir aritmetik işlecin isteğe bağlı sayıda argüman alabileceği Lisp / Scheme'deki + işlemi ne olacak? (+ 1 2 3) Bunu uygulamanın tek akılcı yolu işlevdir.
Evicatos

1
@Mason Wheeler: Soyutlamanın tersine çevrilmesi ile ne demek istiyorsun?
Giorgio

1
@MasonWheeler Bu, şüphesiz, şimdiye kadar gördüğüm teknik bir konudaki en elle dalgalı Wikipedia girişi. Bazı şüpheli girişler gördüm ama bu sadece ... vay.
Evicatos

1
@MasonWheeler: On Lisp'nin 22. ve 23. sayfalarındaki liste uzunluğu işlevlerinden bahsediyor musunuz? Kuyruk çağrısı sürümü karmaşık olarak yaklaşık 1.2x, hiçbir yerde 3x yakınında değil. Ayrıca soyutlamanın tersine çevrilmesi ile ne demek istediğiniz konusunda net değilim.
Michael Shaw

4

"JVM kuyruk arama optimizasyonunu desteklemediğinden, çok sayıda patlayan yığın öngörüyorum"

Bunu söyleyen herkes (1) kuyruk çağrısı optimizasyonunu veya (2) JVM'yi veya (3) her ikisini de anlamıyor.

Wikipedia'dan kuyruk çağrılarının tanımıyla başlayacağım (Wikipedia'yı sevmiyorsanız, işte bir alternatif ):

Bilgisayar biliminde, kuyruk çağrısı, son eylemi olarak başka bir prosedür içinde gerçekleşen alt program çağrısıdır; daha sonra çağıran prosedür tarafından derhal döndürülen bir dönüş değeri üretebilir

Aşağıdaki kodda, çağrının bar()kuyruğu foo():

private void foo() {
    // do something
    bar()
}

Kuyruk çağrısı optimizasyonu , kuyruk çağrısını gören dil uygulaması normal yöntem çağırma (yığın çerçevesi oluşturur) kullanmadığında, bunun yerine bir şube oluşturduğunda gerçekleşir. Bu bir optimizasyondur, çünkü yığın çerçevesi bellek gerektirir ve bilgileri çerçeveye (dönüş adresi gibi) çerçeveye itmek için CPU döngüleri gerektirir ve çağrı / dönüş çiftinin koşulsuz atlamadan daha fazla CPU döngüsü gerektirdiği varsayılır.

TCO genellikle özyinelemeye uygulanır, ancak tek kullanımı bu değildir. Tüm özyinelemeler için de geçerli değildir. Bir faktöriyeli hesaplamak için basit özyinelemeli kod, kuyruk çağrısı için optimize edilemez, çünkü işlevde gerçekleşen son şey bir çarpma işlemidir.

public static int fact(int n) {
    if (n <= 1) return 1;
    else return n * fact(n - 1);
}

Kuyruk çağrısı optimizasyonunu uygulamak için iki şeye ihtiyacınız vardır:

  • Altyordam çağrılarına ek olarak dallanmayı destekleyen bir platform.
  • Kuyruk çağrısı optimizasyonunun mümkün olup olmadığını belirleyebilen statik bir analizör.

Bu kadar. Başka bir yerde belirttiğim gibi, JVM (diğer Turing-complete mimarisi gibi) bir goto'ya sahip. Koşulsuz bir gitme olur , ancak işlevsellik bir koşullu dal kullanılarak kolayca uygulanabilir.

Statik analiz parçası zor olan şeydir. Tek bir işlev içinde sorun değil. Örneğin, işte bir özyinelemeli Scala işlevi List:

def sum(acc:Int, list:List[Int]) : Int = {
  if (list.isEmpty) acc
  else sum(acc + list.head, list.tail)
}

Bu işlev aşağıdaki bayt koduna dönüşür:

public int sum(int, scala.collection.immutable.List);
  Code:
   0:   aload_2
   1:   invokevirtual   #63; //Method scala/collection/immutable/List.isEmpty:()Z
   4:   ifeq    9
   7:   iload_1
   8:   ireturn
   9:   iload_1
   10:  aload_2
   11:  invokevirtual   #67; //Method scala/collection/immutable/List.head:()Ljava/lang/Object;
   14:  invokestatic    #73; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
   17:  iadd
   18:  aload_2
   19:  invokevirtual   #76; //Method scala/collection/immutable/List.tail:()Ljava/lang/Object;
   22:  checkcast   #59; //class scala/collection/immutable/List
   25:  astore_2
   26:  istore_1
   27:  goto    0

Not goto 0sonunda. Karşılaştırıldığında, eşdeğer bir Java işlevi ( Iteratorbir Scala listesini baş ve kuyruğa bölme davranışını taklit etmek için bir kullanması gerekir ) aşağıdaki bayt koduna dönüşür. Son iki işlemin şimdi bir çağırma olduğunu ve ardından bu özyinelemeli çağırma tarafından üretilen değerin açık bir şekilde geri döndüğünü unutmayın.

public static int sum(int, java.util.Iterator);
  Code:
   0:   aload_1
   1:   invokeinterface #64,  1; //InterfaceMethod java/util/Iterator.hasNext:()Z
   6:   ifne    11
   9:   iload_0
   10:  ireturn
   11:  iload_0
   12:  aload_1
   13:  invokeinterface #70,  1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
   18:  checkcast   #25; //class java/lang/Integer
   21:  invokevirtual   #74; //Method java/lang/Integer.intValue:()I
   24:  iadd
   25:  aload_1
   26:  invokestatic    #43; //Method sum:(ILjava/util/Iterator;)I
   29:  ireturn

Tek bir fonksiyonun Kuyruk çağrı optimizasyonu önemsiz şudur: yerine böylece derleyici, aramanın sonucunu kullanan kod bulunmadığından olduğunu görebilirsiniz çağırma işlemi bir ile goto.

Hayatın zorlaştığı yer birden fazla yönteminiz varsa. JVM'nin dallanma talimatları, 80x86 gibi genel amaçlı bir işlemcinin aksine, tek bir yöntemle sınırlıdır. Özel yöntemleriniz varsa hala nispeten basittir: derleyici bu yöntemleri uygun şekilde satır içi yapmakta serbesttir, bu nedenle kuyruk çağrılarını optimize edebilir (bunun nasıl çalıştığını merak ediyorsanız, switchdavranışı kontrol etmek için bir ortak yöntem kullanmayı düşünün ). Bu tekniği aynı sınıftaki birden çok genel yönteme bile genişletebilirsiniz: derleyici yöntem gövdelerini sıralar, genel köprü yöntemleri sağlar ve dahili çağrılar atlamalara dönüşür.

Ancak, özellikle arabirimler ve sınıf yükleyiciler ışığında, farklı sınıflardaki genel yöntemleri düşündüğünüzde bu model bozulur. Kaynak düzeyinde derleyici, kuyruk çağrısı optimizasyonlarını uygulamak için yeterli bilgiye sahip değildir. Ancak, aksine bunun olmadığını aslında gerçekleştirir bilmiyorum "çıplak metal" uygulamaları, * JVM ((en azından, eski Güneş derleyici yapar). Hotspot derleyici şeklinde Bunu yapmak için bilgi var kuyruk çağrı optimizasyonları ve şüpheli değil, ama olabilir .

Bu da beni sorunuzun ikinci kısmına getiriyor, hangisini "umursalıyız?"

Açıkça görüldüğü gibi, diliniz yinelemenin tek ilkesi olarak özyinelemeyi kullanıyorsa, önemsiyorsunuz. Ancak, bu özelliğe ihtiyaç duyan diller onu uygulayabilir; tek sorun, söz konusu dil için bir derleyicinin rasgele bir Java sınıfı tarafından çağrılabilen ve çağrılabilen bir sınıf üretip üretemeyeceğidir.

Bu davanın dışında, ilgisiz olduğunu söyleyerek aşağı oy davet edeceğim. Gördüğüm özyinelemeli kodların çoğu (ve birçok grafik projesiyle çalıştım) kuyruk çağrısı optimize edilemez . Basit faktöriyör gibi, durumu oluşturmak için özyineleme kullanır ve kuyruk işlemi birleşimdir.

Kuyruk çağrısı optimize edilebilir kod için, bu kodu tekrarlanabilir bir forma çevirmek genellikle kolaydır. Örneğin, sum()daha önce gösterdiğim bu işlev genelleştirilebilir foldLeft(). Kaynağa bakarsanız , bunun aslında yinelemeli bir işlem olarak uygulandığını göreceksiniz. Jörg W Mittag'ın işlev çağrıları yoluyla uygulanan bir durum makinesi örneği vardı; atlamalara çevrilen işlev çağrılarına dayanmayan çok sayıda verimli (ve bakım yapılabilir) durum makinesi uygulaması vardır.

Tamamen farklı bir şeyle bitireceğim. SICP'deki dipnotlardan Google'a doğru yol alırsanız, buraya gelebilirsiniz . Ben şahsen benim derleyici yerine sahip altında olması durumunda çok daha ilginç bir yer bulmak JSRtarafından JUMP.


Bir kuyruk çağrısı op kodu mevcutsa, neden kuyruk çağrısı optimizasyonu, her çağrı sitesinde, çağrıyı yapan yöntemin daha sonra herhangi bir kod yürütmesi gerekip gerekmediğini gözlemlemekten başka bir şey gerektirmesin ki? Bazı durumlarda, gibi bir ifade , yığını işlemek ve bir atlama gerçekleştirmek için kod üretmekten daha fazla return foo(123);astar fooile gerçekleştirilebilir, ancak kuyruk çağrısının neden sıradan bir çağrıdan farklı olacağını anlamıyorum. bu konuda.
supercat

@supercat - Sorunuzun ne olduğundan emin değilim. Bu yazının ilk noktası, derleyicinin tüm potansiyel callees'lerin yığın çerçevesinin neye benzeyeceğini bilemeyeceğidir (yığın çerçevesinin sadece işlev argümanlarını değil, aynı zamanda yerel değişkenlerini de içerdiğini unutmayın). Bir çalışma zamanı uyumlu çerçeveler için kontrol yapan bir opcode ekleyebiliriz, ancak bu beni yazının ikinci bölümüne getiriyor: gerçek değer nedir?
kdgregory
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.