[첫화면으로]Perl/익명서브루틴의재귀호출

마지막으로 [b]

1. 개요
2. 재귀호출하는 익명 서브루틴을 만들 때의 문제점
3. 메모리 누수 문제 해결을 위한 코드
4. CPAN 모듈 사용
5. 참고 문서
6. Comments

1. 개요

Perl에서 재귀적으로 호출되는 익명 서브루틴을 만들려고 하니 문제가 생겨서, 이것저것 뒤져본 내용.

2. 재귀호출하는 익명 서브루틴을 만들 때의 문제점

재귀 호출을 써서 팩토리알을 계산하는 서브루틴은 다음과 같이 만들 수 있다:
sub fac {
    my $n = shift;
    return 1 if $n == 1;
    return $n * fac( $n-1 );
}

print "10! = ", fac(10), "\n";

그런데, 익명 서브루틴으로 다음과 같이 만들 수는 없다:
my $fac = sub {
    my $n = shift;
    return 1 if $n == 1;
    return $n * $fac->( $n-1 ); # 여기서 컴파일 에러
};

print "10! = ", $fac->(10), "\n";

변수 선언을 서브루틴 정의보다 먼저 해 주어서 해결할 수는 있다:
my $fac;
$fac = sub {
    my $n = shift;
    return 1 if $n == 1;
    return $n * $fac->( $n-1 );
};

print "10! = ", $fac->(10), "\n";

그런데, 이렇게 하면 $fac 은 익명 서브루틴을 참조하고 있고, 이 서브루틴은 $fac 변수를 참조한다. 따라서 순환 참조가 생기고, 스코프를 벗어난 후에도 메모리에서 제거가 되지 않는다:
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:";
<STDIN>;
Upload:recursive_anon_sub_ver4.png

3. 메모리 누수 문제 해결을 위한 코드

이를 해결하기 위해서, $fac을 weak reference로 만들어 주어야 한다:
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

그런데, 인터넷에서 검색하다 찾은 글[1]에서는 weaken()은 서브루틴을 호출하기 전에 수행해야 한다고 한다. (호출하기 전과 후에 할 때 어떤 차이가 있는지 주인장은 잘 모르겠다.) 문제는, weaken()을 수행하고 나면 그 시점에서 익명 서브루틴의 레퍼런스 카운트가 0이 되어 버려서 없어져 버린다는 거다. 따라서, 다음과 같은 트릭이 필요하다 (이 트릭 역시 [1]에 언급됨)

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";
}
Upload:recursive_anon_sub_ver6.png

4. CPAN 모듈 사용

이 외에, 이 문제를 해결하기 위한 Cpan:Sub::Recursive 또는 Cpan:Sub::Current 등의 모듈도 있다. 모듈을 사용할 경우의 장점은 앞에서와 같은 트릭을 쓰지 않아도 된다는 것과, 앞에서는 어쨌거나 어떤 변수에 서브루틴 레퍼런스를 담아야 하지만 아래의 모듈을 사용하면 foo( recursive { ... } );와 같이 익명 서브루틴을 생성과 동시에 직접 서브루틴의 인자로 넣을 수 있다는 점 등이다.

Cpan:Sub::Recursive 모듈을 사용한 예:
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";
}
Upload:recursive_anon_sub_ver7.png

Cpan:Sub::Current 모듈을 사용한 예:
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";
}
Upload:recursive_anon_sub_ver8.png

[1]에서는 Sub::Recursive는 local을 사용하는데 이게 비용이 많이 드는 일이라서 Sub::Current가 더 낫다고 판단하고 있다.

위의 팩토리알 코드를 가지고 벤치마크를 해 보았는데 (테스트가 제대로 이뤄졌다는 확신은 없으니 참고만 하자),

5. 참고 문서

6. Comments

이름:  
Homepage:
내용:
 


컴퓨터분류

마지막 편집일: 2012-2-20 10:14 am (변경사항 [d])
1703 hits | Permalink | 변경내역 보기 [h] | 페이지 소스 보기