Perl/익명서브루틴의재귀호출 페이지의 소스 보기
마지막으로 [b]
-- Loading page list... --
내용출력
로그인[l]
Diary
[f]
최근변경내역
[r]
페이지목록[i]
횡설수설[2]
게시판[3]
링크
수정할 수 없습니다: Perl/익명서브루틴의재귀호출 는 읽기 전용 페이지입니다.
== # 개요 == [[Perl]]에서 재귀적으로 호출되는 익명 서브루틴을 만들려고 하니 문제가 생겨서, 이것저것 뒤져본 내용. == # 재귀호출하는 익명 서브루틴을 만들 때의 문제점 == 재귀 호출을 써서 팩토리알을 계산하는 서브루틴은 다음과 같이 만들 수 있다: {{{#!vim perl sub fac { my $n = shift; return 1 if $n == 1; return $n * fac( $n-1 ); } print "10! = ", fac(10), "\n"; }}} 그런데, 익명 서브루틴으로 다음과 같이 만들 수는 없다: {{{#!vim perl my $fac = sub { my $n = shift; return 1 if $n == 1; return $n * $fac->( $n-1 ); # 여기서 컴파일 에러 }; print "10! = ", $fac->(10), "\n"; }}} * 변수
$fac
은 아직 정의되지 않은 상태이기 때문에 컴파일 시점에 에러가 난다. 변수 선언을 서브루틴 정의보다 먼저 해 주어서 해결할 수는 있다: {{{#!vim perl my $fac; $fac = sub { my $n = shift; return 1 if $n == 1; return $n * $fac->( $n-1 ); }; print "10! = ", $fac->(10), "\n"; }}} 그런데, 이렇게 하면 $fac 은 익명 서브루틴을 참조하고 있고, 이 서브루틴은 $fac 변수를 참조한다. 따라서 순환 참조가 생기고, 스코프를 벗어난 후에도 메모리에서 제거가 되지 않는다: {{{#!vim perl foreach ( 0 .. 100000 ) { my $fac; $fac = sub { my $n = shift; return 1 if $n == 1; return $n * $fac->( $n-1 ); }; $fac->(10); # print "10! = ", $fac->(10), "\n"; } print "hit any key:";
; }}} Upload:recursive_anon_sub_ver4.png * 루프 안에서 익명 서브루틴을 생성하고 호출하는 과정을 반복한다. 매 반복 때마다 $fac과 저 익명 서브루틴은 소멸되어야 하지만, 실제로는 소멸되지 않고 계속 쌓인다. * 루프를 십만 번 돌고 나면, 종료 전에 메모리 워킹셋 사이즈가 300MB를 차지한다: == # 메모리 누수 문제 해결을 위한 코드 == 이를 해결하기 위해서, $fac을 weak reference로 만들어 주어야 한다: {{{#!vim perl use Scalar::Util qw/weaken/; foreach ( 0 .. 100000 ) { my $fac; $fac = sub { my $n = shift; return 1 if $n == 1; return $n * $fac->( $n-1 ); }; $fac->(10); weaken($fac); # print "10! = ", $fac->(10), "\n"; } }}} Upload:recursive_anon_sub_ver5.png * 이제는 3MB밖에 차지하지 않는다. 그런데, 인터넷에서 검색하다 찾은 글[http://blogs.perl.org/users/holy_zarquons_singing_fish/2010/10/fun-with-recursive-anonymous-subroutines.html]에서는
weaken()
은 서브루틴을 호출하기 전에 수행해야 한다고 한다. (호출하기 전과 후에 할 때 어떤 차이가 있는지 [[주인장]]은 잘 모르겠다.) 문제는, weaken()을 수행하고 나면 그 시점에서 익명 서브루틴의 레퍼런스 카운트가 0이 되어 버려서 없어져 버린다는 거다. 따라서, 다음과 같은 트릭이 필요하다 (이 트릭 역시 [http://blogs.perl.org/users/holy_zarquons_singing_fish/2010/10/fun-with-recursive-anonymous-subroutines.html]에 언급됨) {{{#!vim perl use Scalar::Util qw/weaken/; foreach ( 0 .. 100000 ) { my ($fac, $f); $f = $fac = sub { # 변수가 하나 더 있다 my $n = shift; return 1 if $n == 1; return $n * $fac->( $n-1 ); }; weaken($fac); # $fac->()을 부르기 전에 weaken() $fac->(10); # print "10! = ", $fac->(10), "\n"; } }}} *
$f
변수를 추가로 사용하여 레퍼런스 카운트를 1늘렸다. Upload:recursive_anon_sub_ver6.png * 메모리 사용량은 앞의 경우와 동일하다. (KB단위라서, 정확히 동일하지 않을 수도 있다) == # CPAN 모듈 사용 == 이 외에, 이 문제를 해결하기 위한 Cpan:Sub::Recursive 또는 Cpan:Sub::Current 등의 모듈도 있다. 모듈을 사용할 경우의 장점은 앞에서와 같은 트릭을 쓰지 않아도 된다는 것과, 앞에서는 어쨌거나 어떤 변수에 서브루틴 레퍼런스를 담아야 하지만 아래의 모듈을 사용하면
foo( recursive { ... } );
와 같이 익명 서브루틴을 생성과 동시에 직접 서브루틴의 인자로 넣을 수 있다는 점 등이다. Cpan:Sub::Recursive 모듈을 사용한 예: {{{#!vim perl use Sub::Recursive; foreach ( 0 .. 100000 ) { my $fac = recursive { my $n = shift; return 1 if $n == 1; return $n * $REC->( $n-1 ); }; $fac->(10); # print "10! = ", $fac->(10), "\n"; } }}} *
sub
키워드 대신에
recursive
를 사용 * 현재 서브루틴의 레퍼런스를 담는
$REC
변수가 익스포트된다. Upload:recursive_anon_sub_ver7.png * 역시 메모리 사용량은 앞에서와 유사하다. Cpan:Sub::Current 모듈을 사용한 예: {{{#!vim perl use Sub::Current; foreach ( 0 .. 100000 ) { my $fac = sub { my $n = shift; return 1 if $n == 1; return $n * ROUTINE->( $n-1 ); }; $fac->(10); # print "10! = ", $fac->(10), "\n"; } }}} * 현재 수행중인 서브루틴의 레퍼런스를 반환하는
ROUTINE->()
을 제공한다. Upload:recursive_anon_sub_ver8.png * 역시 메모리 사용량은 앞에서와 유사하다. [http://blogs.perl.org/users/holy_zarquons_singing_fish/2010/10/fun-with-recursive-anonymous-subroutines.html]에서는 Sub::Recursive는
local
을 사용하는데 이게 비용이 많이 드는 일이라서 Sub::Current가 더 낫다고 판단하고 있다. 위의 팩토리알 코드를 가지고 벤치마크를 해 보았는데 (테스트가 제대로 이뤄졌다는 확신은 없으니 참고만 하자), * 일단 익명 서브루틴을 만든 후에, 호출만 십만번 반복하게 했을 때는 Sub::Recursive 가 더 빨랐다. 그리고 5!, 10!, 15!, 20!, 25!을 계산할 때 속도 차이가 더 벌어졌다. * 익명 서브루틴을 생성하고 한 번 호출하는 과정 전체를 루프 안에 넣고 십만번 반복했을 때는, 5!,10!,15! 계산 때는 Sub::Current 가 더 빨랐고, 20!,25! 때는 Sub::Recursive가 점점 더 빨라졌다. * 요컨데 이 결과만 놓고 보면, 익명 서브루틴을 생성하는 과정에서는 Sub::Current가 더 빠른데, 생성한 서브루틴을 호출할 때, 특히 재귀 호출의 단계가 깊어질수록 Sub::Recursive가 더 빨라진다. == # 참고 문서 == * [http://blogs.perl.org/users/holy_zarquons_singing_fish/2010/10/fun-with-recursive-anonymous-subroutines.html] Holy Zarquon's Singing Fish at blogs.perl.org: Fun with recursive anonymous subroutines * Cpan:Sub::Recursive * Cpan:Sub::Current * [http://www.effectiveperlprogramming.com/blog/1503 Use __SUB__ to get a reference to the current subroutine | The Effective Perler] - 5.16부터는
__SUB__
키워드 사용 가능 == # Comments ==
----
---- [[컴퓨터분류]]
Perl/익명서브루틴의재귀호출
페이지로 돌아가기 |
다른 수정본 보기