Continuation Passing Style

얼랭 스터디에서 배운 Continuation Passing Style. 재귀적으로 정의된 함수를 구현하는 여러 가지 방법이 있습니다.

첫 번째 일반적인 재귀 호출.


fac(0) -> 1;
fac(N) -> N * fac(N - 1).


위와 같은 재귀 호출은 종료 조건까지 갔다가 다시 돌아오면서 남은 계산을 수행해야 합니다. 지역 변수와 돌아갈 주소를 스택에 계속 유지해야 하기 때문에 입력 값이 커지면 Stack Overflow가 나고, 비효율적입니다. 종료 조건까지 갔다가 되돌아올 필요가 없다면 스택도 늘어 나지 않고 최초 호출 지점으로 바로 되돌아 갈 수 있기 때문에 효율적이겠죠. Tail call optimization이 될 수 있도록 Tail recursion으로 바꿔줍니다.


%% Accumulator Passing Style
fac_aps(0, Acc) -> Acc;
fac_aps(N, Acc) -> fac_aps(N - 1, Acc * N).
fac_aps(N) -> fac_aps(N, 1).


일반 재귀 호출 형식을 위와 같은 Accumulator Passing Style로 바꾸는 알고리즘이 없기 때문에 컴퓨터가 자동으로 Accumulator Passing Style로 변환 할 수 없다고 합니다. 즉 효율성을 위해서 사람이 Accumulator Passing Style로 변환을 해줘야 합니다. Continuation Passing Style은 꼬리 재귀 호출이면서도 일반 재귀 호출 형식에서 자동으로 변환할 수 있다고 합니다.


%% Continuation Passing Style
fac_cps(0, Cont) -> Cont(1);
fac_cps(N, Cont) -> fac_cps(N - 1, fun(X) -> Cont(N * X) end).
fac_cps(N) -> fac_cps(N, fun(X) -> X end).


그리고 신기한 것이 입력의 크기가 커지면 Continuation Passing Style이 Accumulator Passing Style보다 빨라졌다가, 더 커지면 다시 느려집니다.


31> c(fac), fac:benchmark(100).
fac_aps: 21us
fac_cps: 38us
ok
32> c(fac), fac:benchmark(10000).
fac_aps: 176273us
fac_cps: 167858us
ok
33> c(fac), fac:benchmark(50000).
fac_aps: 5717974us
fac_cps: 5576236us
ok
34> c(fac), fac:benchmark(100000).
fac_aps: 38130471us
fac_cps: 65731319us
ok

by 만성피로 | 2007/07/29 20:29 | Erlang | 트랙백 | 핑백(1) | 덧글(1)

트랙백 주소 : http://colus.egloos.com/tb/3643185
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Linked at 일상 혹은 이상 : SICP .. at 2008/02/24 14:14

... 여.. SICP : 1.2.1 되돌리거나(recursion) 반복하는(iteration) 프로세스Wikipedia : Tail recursion이피님 블로그 : Continuation Passing StyleProgramming Erlang : 8.9 A Word About Tail RecursionA tail-recursive function can ... more

Commented by jahyun at 2007/08/03 00:31
아.. 얼랭스터디에서 얘기한 CPS가 이거였군요.. 후기에서 CPS나오길래 뭔가 했는데..
CPS 방식으로 하면 N이 0인 패턴에서 종료 조건에 넘어간 Cont로 N * (N-1) * .. * 1인 함수가 전달 되게 되는 방식인건가요?
이번 스터디 못 간게 이래저래 아쉽네요.

:         :

:

비공개 덧글

◀ 이전 페이지다음 페이지 ▶