Ruby는 Tail Call 최적화를 수행합니까?


92

기능적 언어는 재귀를 사용하여 많은 문제를 해결하기 때문에 많은 문제가 TCO (Tail Call Optimization)를 수행합니다. TCO는 해당 함수의 마지막 단계 인 다른 함수 (또는 자체적으로이 기능을 TCO의 하위 집합 인 Tail Recursion Elimination이라고도 함)에서 함수를 호출하여 새 스택 프레임이 필요하지 않게합니다. 오버 헤드와 메모리 사용량을 줄입니다.

Ruby는 분명히 기능적 언어 (람다,지도와 같은 함수 등)에서 여러 개념을 "차용"했습니다. 그래서 궁금합니다. Ruby가 테일 호출 최적화를 수행합니까?

답변:


127

아니요, Ruby는 TCO를 수행하지 않습니다. 그러나, 그것은 또한하지 않습니다 하지 총 소유 비용 (TCO)을 수행합니다.

Ruby 언어 사양에는 TCO에 대한 내용이 없습니다. 그것은 당신이 그것을해야한다고 말하는 것이 아니라 당신 이 그것을 할 수 없다는 것을 또한 말하지 않습니다 . 당신 은 그것에 의지 할 수 없습니다 .

언어 사양이 곳은 계획, 달리 요구 하는 것이 모든 구현이 있어야 총 소유 비용 (TCO)을 수행합니다. 그러나 Guido van Rossum이 Python 구현 TCO를 수행 해서는 안된다는 점을 여러 차례 (마지막 며칠 전)에 매우 명확하게 밝힌 Python 과도 다릅니다 .

Yukihiro Matsumoto는 TCO에 공감하며 모든 구현이이를 지원 하도록 강요하고 싶지는 않습니다 . 안타깝게도 이는 TCO에 의존 할 수 없다는 것을 의미합니다. 그렇지 않으면 코드를 더 이상 다른 Ruby 구현으로 이식 할 수 없습니다.

따라서 일부 Ruby 구현은 TCO를 수행하지만 대부분은 수행하지 않습니다. 예를 들어 YARV는 TCO를 지원하지만 (현재로서는) TCO를 활성화하기 위해 소스 코드의 한 줄을 명시 적으로 주석 해제하고 VM을 다시 컴파일해야합니다. 이후 버전에서는 구현이 입증 된 후 기본적으로 설정 될 것입니다. 안정된. Parrot Virtual Machine은 기본적으로 TCO를 지원하므로 Cardinal도이를 매우 쉽게 지원할 수 있습니다. CLR은 TCO를 일부 지원하므로 IronRuby와 Ruby.NET이이를 수행 할 수 있습니다. Rubinius도 그렇게 할 수 있습니다.

하지만 JRuby와 XRuby는 TCO를 지원하지 않으며 JVM 자체가 TCO를 지원하지 않는 한 지원하지 않을 것입니다. 문제는 이것입니다. 빠른 구현을 원하고 Java와 빠르고 원활하게 통합하려면 Java와 스택 호환이 가능하고 JVM 스택을 가능한 많이 사용해야합니다. 트램폴린 또는 명시 적 연속 전달 스타일로 TCO를 매우 쉽게 구현할 수 있지만 더 이상 JVM 스택을 사용하지 않습니다. 즉, Java를 호출하거나 Java에서 Ruby로 호출 할 때마다 일종의 수행해야합니다. 느린 변환입니다. 따라서 XRuby와 JRuby는 TCO 및 연속성 (기본적으로 동일한 문제가 있음)보다 속도 및 Java 통합을 선택했습니다.

이는 기본적으로 TCO를 지원하지 않는 일부 호스트 플랫폼과 긴밀하게 통합하려는 Ruby의 모든 구현에 적용됩니다. 예를 들어, MacRuby도 같은 문제를 겪을 것 같습니다.


2
착각 할 수도 있지만 (그렇다면 계몽 해주세요) 테일 호출이 호출자 스택 프레임을 재사용 할 수 있어야하므로 TCO가 진정한 OO 언어에서 의미가 있는지 의심합니다. 후기 바인딩을 사용하면 어떤 메서드가 메시지 전송에 의해 호출 될지 컴파일 타임에 알 수 없기 때문에 (유형 피드백 JIT를 사용하거나 메시지의 모든 구현자가 스택 프레임을 사용하도록 강제하여 동일한 크기의 또는 TCO를 동일한 메시지의 자체 전송으로 제한하여 ...).
Damien Pollet

2
훌륭한 반응입니다. 이 정보는 Google을 통해 쉽게 찾을 수 없습니다. yarv가 그것을 지원한다는 것이 흥미 롭습니다.
Charlie Flowers

15
Damien, 실제 OO 언어 에는 TCO가 실제로 필요 하다는 것이 밝혀 졌습니다 . projectfortress.sun.com/Projects/Community/blog/…를 참조하십시오 . 스택 프레임에 대해 너무 걱정하지 마십시오. 스택 프레임을 현명하게 설계하여 TCO와 잘 작동하도록하는 것이 완벽하게 가능합니다.
Tony Garnock-Jones

2
tonyg 여기 미러링 GLS '멸종에서 참조 게시물을 저장 : eighty-twenty.org/index.cgi/tech/oo-tail-calls-20111001.html
프랭크 Shearar

나는 임의의 깊이의 중첩 배열 세트를 분해해야하는 숙제를 하고 있습니다. 이를 수행하는 명백한 방법은 재귀 적이며 온라인에서 유사한 사용 사례 (찾을 수있는)는 재귀를 사용합니다. 내 특정 문제는 TCO 없이도 폭발 할 가능성이 거의 없지만 반복으로 전환하지 않고는 완전히 일반적인 솔루션을 작성할 수 없다는 사실이 나를 괴롭 힙니다.
Isaac Rabinovitch 2013 년

42

업데이트 : 다음은 Ruby의 TCO에 대한 멋진 설명입니다. http://nithinbekal.com/posts/ruby-tco/

업데이트 : tco_method gem을 확인하세요 : http://blog.tdg5.com/introducing-the-tco_method-gem/

Ruby MRI (1.9, 2.0 및 2.1)에서 다음을 사용하여 TCO를 켤 수 있습니다.

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

Ruby 2.0에서 기본적으로 TCO를 켜는 제안이있었습니다. 또한 그와 함께 제공되는 몇 가지 문제에 대해 설명합니다. 테일 호출 최적화 : 기본적으로 활성화 하시겠습니까?.

링크에서 발췌 :

일반적으로 꼬리 재귀 최적화에는 "점프"변환에 대한 "호출"이라는 또 다른 최적화 기술이 포함됩니다. 제 생각에는 Ruby의 세계에서 "재귀"를 인식하는 것이 어렵 기 때문에이 최적화를 적용하기가 어렵습니다.

다음 예. "else"절의 fact () 메서드 호출은 "테일 호출"이 아닙니다.

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

fact () 메서드에서 tail-call 최적화를 사용하려면 다음과 같이 fact () 메서드를 변경해야합니다 (연속 전달 스타일).

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

12

다음을 가질 수 있지만 보장되지는 않습니다.

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


현재 링크가 끊어졌습니다.
karatedog

@karatedog : 감사합니다, 업데이트되었습니다. 솔직히 말해서 참조는 아마도 구식 일 것입니다. 버그는 이제 5 년이되었고 그 이후로 동일한 주제에 대한 활동이 있었기 때문입니다.
Steve Jessop 2014

예 :-) 방금 주제에 대해 읽었고 Ruby 2.0에서 소스 코드에서 활성화 할 수 있음을 알았습니다 (더 이상 C 소스 수정 및 재 컴파일 없음).
karatedog


2

이것은 Jörg와 Ernest의 답변을 기반으로합니다. 기본적으로 구현에 따라 다릅니다.

MRI 작업에 대한 Ernest의 답변을 얻지 못했지만 가능합니다. MRI 1.9 ~ 2.1에서 작동하는 이 예제 를 찾았습니다 . 이것은 매우 많은 수를 인쇄해야합니다. TCO 옵션을 true로 설정하지 않으면 "stack too deep"오류가 발생합니다.

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.