테일 콜 최적화 는 많은 언어와 컴파일러에 있습니다. 이 상황에서 컴파일러는 다음 형식의 기능을 인식합니다.
int foo(n) {
...
return bar(n);
}
여기서 언어는 반환되는 결과가 다른 함수의 결과임을 인식하고 새 스택 프레임이있는 함수 호출을 점프로 변경합니다.
고전적인 계승 방법을 실현하십시오.
int factorial(n) {
if(n == 0) return 1;
if(n == 1) return 1;
return n * factorial(n - 1);
}
입니다 하지 때문에 반환에 필요한 검사의 꼬리 호출 optimizatable은. ( 예제 소스 코드 및 컴파일 된 출력 )
이 테일 콜을 최적화하려면
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);
}
이 코드를 컴파일하면 gcc -O2 -S fact.c
(컴파일러에서 최적화를 활성화하려면 -O2가 필요하지만, -O3의 최적화가 많으면 사람이 읽기가 어렵습니다 ...)
_fact(int, int):
cmpl $1, %edi
movl %esi, %eax
je .L2
.L3:
imull %edi, %eax
subl $1, %edi
cmpl $1, %edi
jne .L3
.L2:
rep ret
( 예제 소스 코드 및 컴파일 된 출력 )
하나는 (새로운 스택 프레임으로 서브 루틴 호출을 수행함) 보다는 segment .L3
에서 볼 수 있습니다 .jne
call
Java로 테일 콜 최적화는 어렵고 JVM 구현에 달려 있습니다. - 어떤) TCO는 피할 것입니다 - 꼬리 재귀 + 자바 와 꼬리 재귀 + 최적화 찾아 좋은 태그 집합입니다. 다른 JVM 언어는 꼬리 재귀를 더 잘 최적화 할 수 있습니다 (클로저 시도 ( 꼬리 호출 최적화 를 요구하는 반복 ) 또는 스칼라).
그것은 말했다
당신이 옳은 것을 썼다는 것을 아는 데는 기쁨 이 있습니다.
그리고 지금, 나는 스카치를 가지고 독일 일렉트로니카를 입을 것입니다 ...
"재귀 알고리즘에서 스택 오버플로를 피하는 방법"의 일반적인 질문에 ...
또 다른 방법은 재귀 카운터를 포함하는 것입니다. 이것은 제어 할 수없는 상황 (및 코딩 불량)으로 인한 무한 루프를 감지하기위한 것입니다.
재귀 카운터는
int foo(arg, counter) {
if(counter > RECURSION_MAX) { return -1; }
...
return foo(arg, counter + 1);
}
전화를 걸 때마다 카운터가 증가합니다. 카운터가 너무 커지면 오류가 발생합니다 (여기서는 -1 만 반환하지만 다른 언어에서는 예외를 throw하는 것이 좋습니다). 생각은 재귀를 수행 할 때 예상보다 훨씬 깊고 무한 루프가 발생할 때 더 나쁜 일 (메모리 오류에서)이 발생하는 것을 방지하는 것입니다.
이론적으로는 필요하지 않습니다. 실제로, 나는 약간의 작은 오류와 나쁜 코딩 관행 (다른 스레드가 무언가를 변경하여 다른 스레드가 무한한 재귀 호출 루프로 들어가는 다중 스레드 동시성 문제)으로 인해 코드에 잘못 작성된 코드를 보았습니다.
올바른 알고리즘을 사용하고 올바른 문제를 해결하십시오. 특히 콜라 츠 추측 위해, 나타납니다 당신이 그것을 해결하기 위해 노력하는 것을 XKCD의 방법 :
당신은 숫자로 시작해서 나무 순회를하고 있습니다. 이로 인해 검색 공간이 매우 넓습니다. 정답에 대한 반복 횟수를 계산하는 빠른 실행은 약 500 단계로 이루어집니다. 작은 스택 프레임의 재귀에는 문제가되지 않습니다.
재귀 솔루션을 아는 것은 좋지 않은 일이지만 반복 솔루션 이 더 낫다는 것을 여러 번 알고 있어야합니다 . 재귀 알고리즘을 반복적 인 알고리즘으로 변환하는 방법에는 스택 오버플 로에서 재귀에서 반복으로 이동하는 여러 가지 방법이 있습니다.