Ruby Kuyruk Çağrısı Optimizasyonu gerçekleştirir mi?


92

İşlevsel diller, birçok sorunu çözmek için özyinelemenin kullanılmasına yol açar ve bu nedenle çoğu, Kuyruk Çağrısı Optimizasyonu (TCO) gerçekleştirir. TCO, başka bir işlevden (veya kendisinden, bu durumda bu özellik TCO'nun bir alt kümesi olan Kuyruk Özyineleme Eliminasyonu olarak da bilinir) bir işleve yeni bir yığın çerçevesine ihtiyaç duyulmaması için o işlevin son adımı olarak çağrılara neden olur, bu da ek yükü ve bellek kullanımını azaltır.

Ruby açıkça işlevsel dillerden (lambdas, harita gibi işlevler vb.) Bir dizi kavramı "ödünç almış", bu da beni meraklandırıyor: Ruby kuyruk arama optimizasyonu yapıyor mu?

Yanıtlar:


128

Hayır, Ruby TCO gerçekleştirmez. Ancak, TCO da gerçekleştirmez.

Ruby Dil Spesifikasyonu TCO hakkında hiçbir şey söylemiyor. Bu bunu yapmak zorunda demiyor, ama aynı zamanda demiyor edemez bunu. Ona güvenemezsin .

Dil Şartname burada Planı, aksine gerektirir olduğu tüm uygulamalar gerekir TCO'nuzu gerçekleştirin. Ancak Guido van Rossum, Python Uygulamalarının toplam sahip olma maliyetini gerçekleştirmemesi gerektiğini birkaç kez (son kez birkaç gün önce) açıkça ortaya koyduğu Python'dan farklıdır .

Yukihiro Matsumoto, TCO'ya sempati duyuyor, sadece tüm Uygulamaları onu desteklemeye zorlamak istemiyor . Maalesef bu, TCO'ya güvenemeyeceğiniz anlamına gelir veya güveniyorsanız, kodunuz artık diğer Ruby Uygulamalarına taşınabilir olmayacaktır.

Bu nedenle, bazı Ruby Uygulamaları TCO gerçekleştirir, ancak çoğu yapmaz. Örneğin YARV, TCO'yu destekler, ancak (şu an için) TCO'yu etkinleştirmek için kaynak koddaki bir satırı açıkça kaldırmanız ve sanal makineyi yeniden derlemeniz gerekir - gelecekteki sürümlerde, uygulama kanıtlandıktan sonra varsayılan olarak açık olacaktır. kararlı. Parrot Sanal Makinesi, TCO'yu yerel olarak destekler, bu nedenle Cardinal de bunu kolaylıkla destekleyebilir. CLR, TCO için bir miktar desteğe sahiptir, bu da IronRuby ve Ruby.NET'in muhtemelen yapabileceği anlamına gelir. Rubinius da muhtemelen yapabilirdi.

Ancak JRuby ve XRuby, TCO'yu desteklemiyor ve JVM'nin kendisi TCO için destek sağlamadığı sürece muhtemelen desteklemeyecek. Sorun şudur: Hızlı bir uygulama ve Java ile hızlı ve sorunsuz entegrasyona sahip olmak istiyorsanız, Java ile yığın uyumlu olmalı ve mümkün olduğunca JVM yığınını kullanmalısınız. TCO'yu trambolinler veya açık bir şekilde devam eden geçiş stiliyle kolayca uygulayabilirsiniz, ancak artık JVM yığınını kullanmıyorsunuz, yani Java'ya veya Java'dan Ruby'ye her çağrı yapmak istediğinizde, bir tür yavaş olan dönüşüm. Bu nedenle, XRuby ve JRuby, TCO ve süreklilikler (temelde aynı problemi olan) üzerinden hız ve Java entegrasyonunu tercih ettiler.

Bu, TCO'yu yerel olarak desteklemeyen bazı barındırma platformlarıyla sıkı bir şekilde entegre etmek isteyen tüm Ruby uygulamaları için geçerlidir. Örneğin, MacRuby'nin de aynı sorunu yaşayacağını tahmin ediyorum.


2
Yanılmış olabilirim (öyleyse lütfen beni aydınlatın), ancak TCO'nun gerçek OO dillerinde mantıklı olduğundan şüpheliyim, çünkü kuyruk çağrısının arayan yığın çerçevesini yeniden kullanabilmesi gerekir. Geç bağlama ile, derleme sırasında hangi yöntemin bir mesaj gönderimi tarafından çağrılacağı bilinmediğinden, bunu sağlamak zor görünüyor (belki bir tür geri bildirim JIT ile veya bir mesajın tüm uygulayıcılarını yığın çerçevelerini kullanmaya zorlayarak) aynı boyutta veya TCO'yu aynı mesajın kendi kendine gönderimi ile sınırlandırarak…).
Damien Pollet

2
Bu harika bir cevap. Bu bilgi Google aracılığıyla kolayca bulunmaz. Yarv'ın desteklemesi ilginç.
Charlie Flowers

15
Damien, TCO'nun aslında gerçek OO dilleri için gerekli olduğu ortaya çıktı : projectfortress.sun.com/Projects/Community/blog/… adresine bakın . Yığın çerçevesi hakkında çok fazla endişelenmeyin: Yığın çerçevelerini, TCO ile iyi çalışacak şekilde makul bir şekilde tasarlamak tamamen mümkündür.
Tony Garnock-Jones

2
tonyg, GLS'nin referanslı gönderisini yok oluştan kurtardı ve buraya yansıtarak: seksen-twenty.org/index.cgi/tech/oo-tail-calls-20111001.html
Frank Shearar

Bir dizi rastgele derinlikte iç içe geçmiş dizileri sökmemi gerektiren bir ev ödevi yapıyorum . Bunu yapmanın açık bir yolu özyinelemeli ve çevrimiçi benzer kullanım durumları (bulabildiğim) özyinelemeyi kullanıyor. Benim sorunumun TCO olmasa bile patlaması pek olası değil, ancak yinelemeye geçmeden tamamen genel bir çözüm yazamamak beni rahatsız ediyor.
Isaac Rabinovitch

42

Güncelleme: Ruby'de TCO'nun güzel açıklaması: http://nithinbekal.com/posts/ruby-tco/

Güncelleme: Ayrıca tco_method gem'e de göz atmak isteyebilirsiniz : http://blog.tdg5.com/introducing-the-tco_method-gem/

Ruby MRI'da (1.9, 2.0 ve 2.1) TCO'yu şu şekilde açabilirsiniz:

RubyVM::InstructionSequence.compile_option = {
  :tailcall_optimization => true,
  :trace_instruction => false
}

Ruby 2.0'da varsayılan olarak TCO'yu açmak için bir teklif vardı. Ayrıca bununla birlikte gelen bazı sorunları da açıklar: Kuyruk arama optimizasyonu: varsayılan olarak etkinleştirilsin mi?

Bağlantıdan kısa alıntı:

Genel olarak, kuyruk yineleme optimizasyonu başka bir optimizasyon tekniğini içerir - "atlamak" çevirisine "çağrı". Bence bu optimizasyonu uygulamak zor çünkü Ruby'nin dünyasında "özyinelemeyi" tanımak zor.

Sonraki örnek. "else" cümlesindeki fact () yöntem çağrısı bir "kuyruk çağrısı" değildir.

def fact(n) 
  if n < 2
    1 
 else
   n * fact(n-1) 
 end 
end

Fact () yönteminde kuyruk çağrısı optimizasyonunu kullanmak istiyorsanız, fact () yöntemini aşağıdaki gibi değiştirmeniz gerekir (devam etme stili).

def fact(n, r) 
  if n < 2 
    r
  else
    fact(n-1, n*r)
  end
end

12

Sahip olabilir, ancak garanti edilmez:

https://bugs.ruby-lang.org/issues/1256


Bağlantı şu an itibariyle öldü.
karatedog

@karatedog: teşekkürler, güncellendi. Dürüst olmak gerekirse, hata şu anda 5 yaşında olduğu ve o zamandan beri aynı konuda faaliyetler olduğu için referans muhtemelen güncel değil.
Steve Jessop

Evet :-) Sadece konuyu okudum ve Ruby 2.0'da kaynak kodundan etkinleştirilebileceğini gördüm (artık C kaynak değişikliği ve yeniden derleme yok).
karatedog


2

Bu, Jörg ve Ernest'in cevaplarına dayanıyor. Temelde uygulamaya bağlıdır.

MRI üzerinde çalışmak için Ernest'in cevabını alamadım ama yapılabilir. MRI 1.9 ila 2.1 için çalışan bu örneği buldum . Bu çok büyük bir sayı basmalıdır. TCO seçeneğini doğru olarak ayarlamazsanız, "yığın çok derin" hatası almalısınız.

source = <<-SOURCE
def fact n, acc = 1
  if n.zero?
    acc
  else
    fact n - 1, acc * n
  end
end

fact 10000
SOURCE

i_seq = RubyVM::InstructionSequence.new source, nil, nil, nil,
  tailcall_optimization: true, trace_instruction: false

#puts i_seq.disasm

begin
  value = i_seq.eval

  p value
rescue SystemStackError => e
  p e
end
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.