2014-04-30 2 views
10

루비는 왼쪽 재귀를 마주 칠 때 더 나은 성능을 보이는 것으로 나타났습니다. 예를 들면 : 나는이 문제에 대한 설명을 찾을 수 없습니다루비 왼쪽 대 오른쪽 재귀

left_recursive_factorial(9001) 
    # => factorial of 9001 

right_recursive_factorial(9001) 
    # => SystemStackError: stack level too deep 
    # from (pry):6:in `right_recursive_factorial' 

:

def left_recursive_factorial(number) 
    return 1 if number.zero? 
    left_recursive_factorial(number.pred) * number 
end 

def right_recursive_factorial(number) 
    return 1 if number.zero? 
    number * right_recursive_factorial(number.pred) 
end 

내가 9000 이상 값이 방법() 나는 다른 결과를 얻을 전화

.

약간의 관련이있는 유일한 것은 왼쪽 재귀에 문제가있는 파서 LL()이었고 주위를 뒤집을 수는 있지만 그다지 파고 들지 않았습니다.

왼쪽과 오른쪽 재귀가 다르게 (일반적으로 Ruby에서) 반복적으로 수행되는 원인에 대해 조금 더 자세히 설명해 주시겠습니까? 하나를 선택하거나 다른 것을 선택할 가능성이있는 이유는 무엇입니까? (그리고 왜 남았습니까? 루비에서 선택)?

+0

루비는 왼쪽 앞에 곱셈의 오른쪽을 평가하는 것으로 보이므로 왼쪽 버전은 [꼬리 재귀] (https://en.wikipedia.org/wiki/Tail_call)를 사용하므로 필요하지 않습니다. 스택에 추가하십시오. – clcto

+0

@clcto : 이것은 꼬리 전화 제거처럼 보이지 않습니다. 하나의 경우, 곱셈은 재귀 호출이 아닌 함수의 마지막 연산입니다. 또 다른 하나는 9500으로 숫자를 올리면 여전히 스택을 날려 버릴 것입니다. – Chuck

+4

@clcto 루비는 오른쪽에서 왼쪽으로 피연산자와 메소드 인수를 왼쪽에서 오른쪽으로 가장 정확하게 평가합니다. 또한 피연산자가 평가되는 순서는 무언가가 꼬리 재귀인지 여부와 관련이 없습니다. 곱셈은 ​​반드시 함수 호출 이후에 발생합니다 (두 숫자를 모두 알기 전까지 두 숫자를 곱할 수 없기 때문에). 따라서 어떤 숫자가 먼저 평가 되더라도 곱셈은 꼬리 재귀가 아닙니다. 그리고 표준 루비 인터프리터는 꼬리 재귀를 최적화하지 않습니다. – sepp2k

답변

6

좋아, 나는 YARV 해커가 아니지만 여기 이해할 수있는 차이가있다. 메소드를 호출하면, 송신자는 메소드의 인수를 스택에 푸시합니다. 호출 된 메소드는 인수를 해제하고 리턴 값을 push합니다. 재귀 호출을 먼저 수행하면 number 인수가 아직 스택에 푸시되지 않았으므로 각 호출 스택에 약간의 공간이 필요합니다. 이것이 그 버전에서 반복을 몇 번 더 얻을 수 있지만 그 이상은 아닙니다. 스택 사용량을 몇 % 줄이려고합니다.

+0

이것은 의미가 있지만 좀 더 자세한 대답을 원했습니다. 예를 들어 python과 php가 무관심한 이유는 무엇입니까? 반면 js는 비슷한 동작을 보여줍니다. – ndn

+1

@ndn : 이것은 언어 구현에 크게 의존합니다. 예를 들어, 파이썬에는 재귀 제한이 있습니다. 실제 값은 인터프리터가 재귀 호출에 도달했는지 확인하고 거부합니다. 당신은'sys.setrecursionlimit()'로 변경할 수 있습니다. PHP의 경우에는 어떻게 작동하는지 전혀 알지 못하기 때문에 디스 어셈 블러를 가져 가야했습니다. 인수를 푸시하고 곱하기를 호출하는 스택 머신 방식은 사용하지 않습니다. 대신 실제로'MUL' 연산 코드의 피연산자로'*'에 피연산자를 부여하므로 함수 사이의 유일한 차이는'MUL! 0, $ 2' 대'MUL $ 2,! 0'입니다. – Chuck