다섯째날 : Inline모듈을 사용하여 Perl의 성능 극복하기 - 몬테카를로 시뮬레이션

글쓴이

@aer0

본문

Perl같은 스크립트언어들은 빠른 개발속도와 뛰어난 표현력으로 해야 할 일을 재빨리 해치울 수 있지만 반면에 그 trade-off로 성능에 있어서는 C나 C++등의 native 바이너리로 컴파일되는 언어들에 비해서 뒤떨어질 수밖에 없습니다. 특히나 많은 루프를 돌며 많은 계산을 해야 하는 작업 같은 경우 그 차이가 더 벌어지게 되죠.

그렇다면 생산성과 성능의 양쪽을 취하고자 한다면 전체적인 구조를 잡고 콘트롤하는 영역은 Perl로 짜고 성능에 있어 Perl로는 병목이 생기는 부분은 native 바이너리로 컴파일된 코드의 성능을 취하면 될 것입니다.

따라서 여기서는 그러한 상황을 모사하기 위해 몬테카를로 시뮬레이션이라는 무작위 숫자의 무수한 반복에 의해 통계적인 방법으로 근사적인 해를 구하는 방법을 사용하여 의도적으로 Perl구현에 있어 병목지점을 만들고 그 병목지점을 Inline모듈을 통해 C코드를 인라인으로 Perl코드에 포함시킴으로써 실제 얼마만큼 성능향상을 꾀할 수 있는지를 보려고 합니다.

그러면 학창시절을 떠올리며 다음과 같이 장반경 a, 단반경 b를 가지는 타원의 경우를 생각해봅시다.

위 타원의 공식은 x^2/a^2 + y^2/b^2 = 1 이며 타원의 면적은 PI*a*b 가 됩니다. 여기서 타원의 면적공식은 모른다고 했을때 몬테카를로 시뮬레이션에 의해서 타원의 면적을 구하려면 다음과 같은 절차를 거치면 됩니다.

위처럼 타원의 1/4 부분을 떼어 놓고 타원곡선 내부의 면적을 생각해 보자면 가로 a, 세로 b의 사각형 영역 내에서 (x,y) 좌표를 무작위로 난수를 n회 발생시켜서 점을 찍습니다. 어느 정도 점을 많이 찍고 그중에 타원곡선 안에 있는 경우가 i번 나왔다고 하면 결국 타원 내부의 면적은 사각형영역면적 곱하기 (i/n)인 a * b * (i/n)이 되고 그 면적이 타원의 면적의 1/4이니 4를 곱하면 결과적으로 a * b * (i/n)*4가 타원의 면적이 되게됩니다. 정밀도를 높이려면 임의의 점 (x,y)를 더 많이 무수히 많이 발생시켜 사각형 영역을 빈틈없이 골고루 덮도록 시행횟수를 더 늘리게 되면 그에 비례하여 더욱 실제 면적에 가까워지게 됩니다.

여기서는 점을 찍는 횟수를 10000번으로 해서 공식에 의한 면적과 몬테카를로 시뮬레이션에 의해 면적을 구하는 프로그램을 먼저 Perl로 구현해보면 다음과 같습니다.

    #!/usr/bin/env perl
    use 5.010;
    use strict;
    use warnings;

    my ($min_a, $max_a) = (11,20);
    my ($min_b, $max_b) = (1,10);

    foreach my $a ($min_a .. $max_a) {
        foreach $b ($min_b .. $max_b) {
            say monte_carlo_ellipse_area_perl($a, $b);
            say 3.141592*$a*$b;
            say "------------";
        }
    }

    sub monte_carlo_ellipse_area_perl {
        my ($a, $b) = @_;
        my ($inner, $outer) = (0, 0);
        my $try = 10000;

        foreach (1 .. $try) {
            (rand($a)**2 / $a**2 + rand($b)**2 / $b**2) < 1  ?  $inner++ : $outer++;
        }
        my $area = ($inner/$try) * $a*$b * 4;
        return $area;
    }

위 코드는 장반경 a가 11에서 20,단반경 b가 1에서 10까지 인 경우 모두에 대해서 루프를 돌며 타원의 면적을 몬테카를로 시뮬레이션을 이용해서 구하는 것입니다. 코드를 실행하면 결과는

    $ time perl monte.pl
    .
    .
    생략
    .
    .
    500.352
    502.65472
    ------------
    562.32
    565.48656
    ------------
    628.8
    628.3184
    ------------

    real    0m0.549s
    user    0m0.540s
    sys     0m0.000s

와 같습니다. 결과를 보면 몬테카를로 시뮬레이션으로 구한 면적과 실제 공식에의한 면적이 비슷하게 나옴을 볼 수 있습니다. 그리고 수행시간은 0.549 초 정도 걸렸습니다.

이제 위 프로그램에서 제일 heavy한 계산을 하는 몬테카를로 시뮬레이션 함수를 Inline모듈을 통해 C함수로 교체하면 다음과 같은 코드를 쓸 수 있습니다.

    #!/usr/bin/env perl
    use 5.010;
    use Inline C;
    use strict;
    use warnings;

    my ($min_a, $max_a) = (11,20);
    my ($min_b, $max_b) = (1,10);

    foreach my $a ($min_a .. $max_a) {
        foreach $b ($min_b .. $max_b) {
            say monte_carlo_ellipse_area_c($a, $b);
            say 3.141592*$a*$b;
            say "------------";
        } 
    }

    __END__
    __C__
    #include <math.h>
    #include <stdlib.h>  /* RAND_MAX */

    double myrand(int var) {
        return ( (double) rand()/ RAND_MAX * var );
    }

    double monte_carlo_ellipse_area_c(int a, int b) {
        int inner = 0;
        int outer = 0;
        int try = 10000;
        double area;

        int i;
        for (i=0; i < try; i++) {
            (pow(myrand(a),2.0) / pow(a,2.0) + pow(myrand(b),2.0) / pow(b,2.0)) < 1
                ?  inner++ : outer++;
        }
        area = ((double) inner/(double) try) * a*b * 4;
        return area;
    }

위 처럼 코드를 작성해서 실행시키면 최초 실행시에 포함된 C코드를 알아서 컴파일해서 Perl에서 호출할 수 있도록 만들어 줍니다. 따라서 최초 실행시는 포함된 C코드 컴파일 및 처리작업에 시간이 걸리지만 두번째 실행에서 부터는 XS모듈처럼 C등으로 작성된 native코드를 직접 호출해서 쓰는것과 마찬가지로 아주 빠른 처리 결과를 보여줍니다. 위 코드를 실행한 결과는 다음과 같습니다.

    $ time perl monte_c.pl
    . 
    .
    생략
    .
    .
    507.648
    502.65472
    ------------
    568.44
    565.48656
    ------------
    621.44
    628.3184
    ------------

    real    0m0.105s
    user    0m0.100s
    sys     0m0.000s

와우! 대충봐도 pure perl 코드에 비해 5배정도 빨라졌음을 확인할 수 있네요.

이처럼 Perl에서는 Inline 모듈을 통해서 SWIG이나 XS모듈처럼 다소 번거롭게 별도로 인터페이스를 만들어 결합시키지 않고도 편리하게 native 바이너리로 컴파일되는 C언어등의 코드를 인라이닝 함으로서 성능향상 및 다양한 언어를 혼합하여 사용할 수 있습니다. 그리고 Inline::C 로 작성한 코드는 InlineX::C2XS모듈 같은 것을 이용하면 Perl코드와 C코드가 분리된 형태의 XS모듈 형태로 쉽게 변환해주는 모듈들도 존재합니다.

Inline모듈에서 지원하는 언어는 https://metacpan.org/search?q=Inline 를 보시면 아시겠지만 C언어 뿐만 아니라 C++, Java, Lua, Python, Ruby, Javascript등 아주 다양합니다.

Perl만으로는 뭔가 부족하다고 생각될 때 Inline모듈을 떠올려보세요~

DISQUS 댓글

FACEBOOK 댓글