April 2009 Archives
본 블로그에 댓글을 남겨주신 liquidbird님의 블로그에서 몬티홀 문제를 보고 재미있어 보여서 나름대로 한번 시뮬레이션 코드를 만들어 봤다.
요즘 Perl커뮤니티에서는 Modern Perl이라는 게 아주 이슈가 되고 있는데 이것은 과거의 숙련되지 않은 스크립터들이 양산해내던 조악한 CGI Perl코드의 이미지를 벗고 그동안 발전해 온 Perl의 최신 모듈 및 사양을 사용하여 세련되고 깔끔한 Perl 코드를 작성하는 것이다.
그 중심에 서 있는 것이 Moose라는 새로운 Perl OOP프레임웍으로 나도 공부한 걸 써볼 겸 Moose를 써서 만들어 보았다.
보통 Python이나 Ruby를 소개하는 글을 보면 빠지지 않고 등장하는 레퍼토리가 Perl이 OOP를 잘 지원하지 못하는데 그것을 개선해서 어쩌고 저쩌고~ 이런 문장인데 Moose를 살펴본다면 앞으로 그런 말은 하지 못할 것이라고 생각된다.
참고: 새로운 Perl OOP로 개과천선하기, Moose관련 슬라이드 자료
<몬티홀 테스트 코드>
<결과>
* 재선택 했을 때
67.6 32.4
68.6 31.4
65.3 34.7
* 재선택 안했을 때( 위 코드에서 $game->rechoose_door() 를 주석처리 )
33.6 66.4
33.5 66.5
32.3 67.7
요즘 Perl커뮤니티에서는 Modern Perl이라는 게 아주 이슈가 되고 있는데 이것은 과거의 숙련되지 않은 스크립터들이 양산해내던 조악한 CGI Perl코드의 이미지를 벗고 그동안 발전해 온 Perl의 최신 모듈 및 사양을 사용하여 세련되고 깔끔한 Perl 코드를 작성하는 것이다.
그 중심에 서 있는 것이 Moose라는 새로운 Perl OOP프레임웍으로 나도 공부한 걸 써볼 겸 Moose를 써서 만들어 보았다.
보통 Python이나 Ruby를 소개하는 글을 보면 빠지지 않고 등장하는 레퍼토리가 Perl이 OOP를 잘 지원하지 못하는데 그것을 개선해서 어쩌고 저쩌고~ 이런 문장인데 Moose를 살펴본다면 앞으로 그런 말은 하지 못할 것이라고 생각된다.
참고: 새로운 Perl OOP로 개과천선하기, Moose관련 슬라이드 자료
<몬티홀 테스트 코드>
#!/usr/bin/perl
use strict;
use warnings;
{
package Door;
use Moose;
use Moose::Util::TypeConstraints;
subtype 'Door::Thing'
=> as 'Str'
=> where { $_ eq 'Sports Car' || $_ eq 'Goat' }
=> message { "Bad thing" };
has thing => ( is => 'rw', isa => 'Door::Thing', default => 'Goat' );
}
{
package MontyHall;
use Moose;
use List::Util qw/shuffle/;
has num_doors => ( is => 'ro', isa => 'Int', default => 3 );
has doors => (
is => 'rw',
isa => 'ArrayRef[Door]',
auto_deref => 1,
lazy_build => 1,
);
has gamer_choice => ( is => 'rw', isa => 'Int' );
has opened_door => ( is => 'rw', isa => 'Int' );
sub _build_doors {
my ($self) = @_;
my @doors = shuffle( Door->new(thing=>'Sports Car'),
map { Door->new() } 1 .. $self->num_doors-1 );
return [@doors];
}
sub choose_door {
my ($self) = @_;
$self->gamer_choice( int(rand( @{$self->doors} )) );
}
sub open_door {
my ($self) = @_;
while (1) {
my $num = int(rand( @{$self->doors} ));
if ( $num != $self->gamer_choice && $self->doors->[$num]->thing eq 'Goat' ) {
$self->opened_door($num);
last;
}
}
}
sub rechoose_door {
my ($self) = @_;
while (1) {
my $num = int(rand( @{$self->doors} ));
if ( $num != $self->opened_door && $num != $self->gamer_choice ) {
$self->gamer_choice($num);
last;
}
}
}
sub result {
my ($self) = @_;
return ( $self->doors->[$self->gamer_choice]->thing eq 'Sports Car' );
}
}
my $success = 0;
my $fail = 0;
my $try = 1000;
foreach (1 .. $try) {
my $game = MontyHall->new();
$game->choose_door();
$game->open_door();
$game->rechoose_door();
$game->result() ? $success++ : $fail++;
}
print $success/$try*100, " ", $fail/$try*100, "\n";
<결과>
* 재선택 했을 때
67.6 32.4
68.6 31.4
65.3 34.7
* 재선택 안했을 때( 위 코드에서 $game->rechoose_door() 를 주석처리 )
33.6 66.4
33.5 66.5
32.3 67.7
일전의 Java VS Perl 이란 글에서 Raymundo님이 문자열 병합의 성능이 LINUX에서는 선형적인데 반해 Windows에서는 특정시점에 이르면 갑자기 차이가 나기 시작한다고 하셔서 직접 테스트해봤다.
참고: Perl의 String Concatenation 속도 측정
테스트는 본인의 Thinkpad X300 노트북에서 했으며 노트북의 사양은 다음과 같다.
[LENOVO:647818K:L3A6651]
[1:Intel(R) Core(TM)2 Duo CPU L7100 @ 1.20GHz:1201MHz:4096 KB:200 MHz]
[4096M:Non-ECC:2048M/2048M]
[60 G:SAMSUNG MCCOE64G8MPP-0VA]
[Microsoft® Windows Vista™ Business K 32bit]
그리고 사용한 Perl은 Strawberry Perl 5.10 이다.
일단 Raymundo님의 코드 중 문제가 되는 부분만 떼어내어 문자열 병합연산을 10000회씩 묶어서 시간을 측정해봤다.
<테스트 코드>
#!/usr/bin/perl
use strict;
use warnings;
use Time::HiRes qw/gettimeofday tv_interval/;
my $str = "1234567890";
my $long_str;
my ($st,$et);
my $sum = 0;
for ( my $i = 1; $i <= 1000000; $i++ ) {
$st = [gettimeofday];
$long_str .= $str;
$et = [gettimeofday];
my $t = tv_interval($st,$et);
$sum += $t;
if ( $i % 10000 == 0 ) {
print "$i $sum\n";
$sum = 0;
}
}
그런데 루프의 횟수가 증가할수록 동일한 횟수의 문자열 병합연산에 걸리는 시간이 거의 선형적으로 증가하였다.
그래서 윈도우즈 내에서 문자열 길이가 길어짐에 따라 메모리를 할당하는 부분에서 무슨 병목이 생기는 것 같아서 루프를 돌며 문자열을 합쳐나가기 전에 미리 최종 문자열 길이만큼 메모리를 확보하면 어떨까 해서 코드를 다음과 같이 고치고 다시 테스트해봤다.
<테스트 코드2>
#!/usr/bin/perl
use strict;
use warnings;
use Time::HiRes qw/gettimeofday tv_interval/;
my $str = "1234567890";
my $long_str = " " x 10000000; # 미리 최종문자열 길이만큼 할당한다.
my ($st,$et);
my $sum = 0;
$long_str=""; # 문자열을 초기화 한다.
for ( my $i = 1; $i <= 1000000; $i++ ) {
$st = [gettimeofday];
$long_str .= $str;
$et = [gettimeofday];
my $t = tv_interval($st,$et);
$sum += $t;
if ( $i % 10000 == 0 ) {
print "$i $sum\n";
$sum = 0;
}
}
위 코드에서 미리 최종문자열 길이만큼 할당하고 루프에 들어가기 전 다시 빈 문자열로 초기화했는데 이렇게 하면 Perl은 해당 변수에 메모리를 할당하고 초기화되어도 재사용 시 효율적인 사용을 위해서 바로 할당받은 메모리를 해제하지 않는다 그래서 이런 테크닉으로 메모리를 미리 할당받을 수 있으며 이같은 테크닉은 Perl의 배열과 해시에서도 사용 가능하다.
$#array = 10000; # 배열의 10001요소 개수만큼 미리 메모리 할당
keys %hash = 10000; # 해시의 10000개 키만큼 미리 메모리 할당
이렇게 해놓고 테스트하니 루프 전반에 걸쳐 소요시간이 다음 그래프와 같이 종전에 선형적으로 증가하던 것이 전체에 걸쳐 일정하게 나왔다.

테스트의 결과에서 미리 메모리가 확보된 상태에서는 문제가 생기지 않음을 알 수 있고 메모리를 동적으로 확보해가는 과정에서 문제가 생겼음을 추측할 수 있다.
확인해보지는 않았지만 Perl에서 메모리를 동적으로 확보해나가는데 realloc 같은 시스템콜 함수를 사용할 것으로 생각되는데 인터넷을 찾다 보니 realloc() on Windows vs. Linux 에 나와 있는 것 처럼 Windows에서 realloc 구현은 linux에 비해 성능상 문제가 있는 것 같다. 그래서 이런 차이가 생기지 않았나 본다.
참고: Perl의 String Concatenation 속도 측정
테스트는 본인의 Thinkpad X300 노트북에서 했으며 노트북의 사양은 다음과 같다.
[LENOVO:647818K:L3A6651]
[1:Intel(R) Core(TM)2 Duo CPU L7100 @ 1.20GHz:1201MHz:4096 KB:200 MHz]
[4096M:Non-ECC:2048M/2048M]
[60 G:SAMSUNG MCCOE64G8MPP-0VA]
[Microsoft® Windows Vista™ Business K 32bit]
그리고 사용한 Perl은 Strawberry Perl 5.10 이다.
일단 Raymundo님의 코드 중 문제가 되는 부분만 떼어내어 문자열 병합연산을 10000회씩 묶어서 시간을 측정해봤다.
<테스트 코드>
#!/usr/bin/perl
use strict;
use warnings;
use Time::HiRes qw/gettimeofday tv_interval/;
my $str = "1234567890";
my $long_str;
my ($st,$et);
my $sum = 0;
for ( my $i = 1; $i <= 1000000; $i++ ) {
$st = [gettimeofday];
$long_str .= $str;
$et = [gettimeofday];
my $t = tv_interval($st,$et);
$sum += $t;
if ( $i % 10000 == 0 ) {
print "$i $sum\n";
$sum = 0;
}
}
그런데 루프의 횟수가 증가할수록 동일한 횟수의 문자열 병합연산에 걸리는 시간이 거의 선형적으로 증가하였다.
그래서 윈도우즈 내에서 문자열 길이가 길어짐에 따라 메모리를 할당하는 부분에서 무슨 병목이 생기는 것 같아서 루프를 돌며 문자열을 합쳐나가기 전에 미리 최종 문자열 길이만큼 메모리를 확보하면 어떨까 해서 코드를 다음과 같이 고치고 다시 테스트해봤다.
<테스트 코드2>
#!/usr/bin/perl
use strict;
use warnings;
use Time::HiRes qw/gettimeofday tv_interval/;
my $str = "1234567890";
my $long_str = " " x 10000000; # 미리 최종문자열 길이만큼 할당한다.
my ($st,$et);
my $sum = 0;
$long_str=""; # 문자열을 초기화 한다.
for ( my $i = 1; $i <= 1000000; $i++ ) {
$st = [gettimeofday];
$long_str .= $str;
$et = [gettimeofday];
my $t = tv_interval($st,$et);
$sum += $t;
if ( $i % 10000 == 0 ) {
print "$i $sum\n";
$sum = 0;
}
}
위 코드에서 미리 최종문자열 길이만큼 할당하고 루프에 들어가기 전 다시 빈 문자열로 초기화했는데 이렇게 하면 Perl은 해당 변수에 메모리를 할당하고 초기화되어도 재사용 시 효율적인 사용을 위해서 바로 할당받은 메모리를 해제하지 않는다 그래서 이런 테크닉으로 메모리를 미리 할당받을 수 있으며 이같은 테크닉은 Perl의 배열과 해시에서도 사용 가능하다.
$#array = 10000; # 배열의 10001요소 개수만큼 미리 메모리 할당
keys %hash = 10000; # 해시의 10000개 키만큼 미리 메모리 할당
이렇게 해놓고 테스트하니 루프 전반에 걸쳐 소요시간이 다음 그래프와 같이 종전에 선형적으로 증가하던 것이 전체에 걸쳐 일정하게 나왔다.

테스트의 결과에서 미리 메모리가 확보된 상태에서는 문제가 생기지 않음을 알 수 있고 메모리를 동적으로 확보해가는 과정에서 문제가 생겼음을 추측할 수 있다.
확인해보지는 않았지만 Perl에서 메모리를 동적으로 확보해나가는데 realloc 같은 시스템콜 함수를 사용할 것으로 생각되는데 인터넷을 찾다 보니 realloc() on Windows vs. Linux 에 나와 있는 것 처럼 Windows에서 realloc 구현은 linux에 비해 성능상 문제가 있는 것 같다. 그래서 이런 차이가 생기지 않았나 본다.
http://thebasis.tistory.com/89 에서 Java와 Perl의 문자열 및 정규표현식 문자열 치환 테스트에 있어 Perl이 Java에 비해 문자열 치환이 4배 정도 느리다는 결과를 보고 뭔가 테스트가 잘못된 것 같아서 직접 테스트를 해봤다.
일단 문자열을 이어 붙이는 작업에서는 Java와 Perl이 별 차이 없다고 하니 따로 테스트는 생략한다. 윗글의 코드에서 사용된 Java의 StringBuilder는 일반 String객체에 비해 때때로는 수백 배 정도 빠르다고 하는데 그것이랑 어떤 특별한 기교를 부리지 않고 Perl의 기본적인 .연산자를 사용한 문자열 병합연산이 비슷한 성능이라고 하니 Perl의 문자열 병합성능은 아주 괜찮다고 볼 수 있겠다.
그다음은 문자열에서 특정 문자열을 치환하는 연산으로 글 쓴 분은 알파벳과 한글이 있는 문자열에서 알파벳 문자열을 _로 바꾸는 코드라고 했는데 실제 소스는 알파벳이 아닌 문자열을 _로 바꾸는 코드이다. 하지만 뭔가 글 적으실때 잘못 적으신 것 같아서 코드의 동작은 그대로 따랐다. 그리고 Perl 예제에서 유니코드를 사용하기 위해서 use encoding 'utf8'; 프래그마를 사용했는데 이것은 설계상 잘 못으로 원 저자가 deprecate시키고 싶다고 한 이제는 쓰지 말아야 할 프래그마이며 ( 자세한 내용은 http://aero.springnote.com/pages/1053508 를 참고) 대신에 Perl 코드에서 유니코드를 사용하려면 소스 인코딩을 utf8으로 하고 use utf8; 프래그마를 사용해야 한다.
다음은 테스트하기 위해 만든 Java와 Perl 코드이다.
<Java>
public class Test {
public static void main(String[] args) {
String a = "abcdefghij가나다라마바사아자차";
StringBuilder sb = new StringBuilder();
for (int i=0; i<1000000; i++ ) {
sb.append(a);
}
String ab = sb.toString();
long sTime, eTime;
sTime = System.currentTimeMillis();
ab = ab.replaceAll("[^a-z]+", "_");
eTime = System.currentTimeMillis();
System.out.println(((eTime-sTime)/1000.)+ " sec");
}
}
<Perl>
#!/usr/bin/perl
use strict;
use warnings;
use Time::HiRes qw/gettimeofday tv_interval/;
use utf8;
my $a = "abcdefghij가나다라마바사아자차";
my $b = $a x 1000000;
my $sTime = [gettimeofday];
$b =~ s/[^a-z]+/_/g;
my $eTime = [gettimeofday];
print tv_interval($sTime, $eTime)," sec\n";
다음은 위 코드들로 3번씩 실행한 결과다.
<Java>
1.392 sec
1.441 sec
1.376 sec
<Perl>
1.564731 sec
1.531707 sec
1.511697 sec
결과를 보면 그렇게 차이가 많이 나지 않는다. use encoding 프래그마는 설계상의 문제점도 있지만 Perl의 내부 I/O서브 시스템을 변경하기 때문에 문자열 처리작업시 부하가 가중된다. 그래서 이것을 use utf8으로 바꾸고 하니 훨씬 빨라졌다.
Java가 Perl보다 빠른 이유는 Java는 내부적으로 문자열을 UTF-16인코딩으로 다루는데 이 인코딩 방법은 surrogate pair가 나오지 않는 이상 기본적으로 16bit(2byte)고정폭 인코딩이므로 문자열 치환 같은 작업 시 고정된 폭으로 메모리상 포인터를 이동하며 빠르게 작업 가능하기 때문이 아닌가 생각된다. 반면 Perl은 유니코드 사용 시 내부인코딩으로 UTF-8을 사용한다. UTF-8은 가변길이 인코딩으로 문자열에서 어떤 포인터를 이동시켜 나가면서 작업하려면 고정폭류 인코딩에 비해서 불리할수 밖에 없기 때문에 성능이 다소 떨어진 것으로 보인다.
그리고 Java가 Perl보다 메모리를 더 많이 사용한다고 하는데 그건 UTF-8인코딩에서 1byte로 표현되는 ASCII 알파벳 문자까지 JAVA는 UTF-16 2byte로 표현하고 문자열을 객체화시켜 다루기 때문에 Java가 더 메모리를 많이 사용할 수밖에 없을 것으로 보인다.
그런데 위 Perl코드를 다시 생각해보면 알파벳이 아닌 문자열을 _로 바꾸는 것이기 때문에 utf8인코딩 문자열에서는 그냥 문자열을 문자당 가변폭 인코딩이 아닌 단순 바이트시퀀스(byte sequence)로 보고 작업을 해도 상관없다. 그렇게 작업하면 일반 ASCII문자열 처럼 1byte고정폭으로 포인터를 이동시켜가며 작업할 것이기 때문에 자바에서 처럼 성능향상을 기대할 수 있을 것이라 추측하고 한 번 use utf8; 프래그마를 빼고 테스트 해보았다.
<결과>
1.060795 sec
1.06102 sec
1.0578 sec
역시나 예상대로 성능의 향상이 있었는데 Java보다 더 빨라졌다.
이제 Perl의 완승(?)
끝으로 Java하는 사람이 Perl의 특징을 이해하고 언어적 시각을 넓히는데 참고할 만한 글을 하나 소개할까 한다.
Why Michael Schwern is not a Java programmer.
일단 문자열을 이어 붙이는 작업에서는 Java와 Perl이 별 차이 없다고 하니 따로 테스트는 생략한다. 윗글의 코드에서 사용된 Java의 StringBuilder는 일반 String객체에 비해 때때로는 수백 배 정도 빠르다고 하는데 그것이랑 어떤 특별한 기교를 부리지 않고 Perl의 기본적인 .연산자를 사용한 문자열 병합연산이 비슷한 성능이라고 하니 Perl의 문자열 병합성능은 아주 괜찮다고 볼 수 있겠다.
그다음은 문자열에서 특정 문자열을 치환하는 연산으로 글 쓴 분은 알파벳과 한글이 있는 문자열에서 알파벳 문자열을 _로 바꾸는 코드라고 했는데 실제 소스는 알파벳이 아닌 문자열을 _로 바꾸는 코드이다. 하지만 뭔가 글 적으실때 잘못 적으신 것 같아서 코드의 동작은 그대로 따랐다. 그리고 Perl 예제에서 유니코드를 사용하기 위해서 use encoding 'utf8'; 프래그마를 사용했는데 이것은 설계상 잘 못으로 원 저자가 deprecate시키고 싶다고 한 이제는 쓰지 말아야 할 프래그마이며 ( 자세한 내용은 http://aero.springnote.com/pages/1053508 를 참고) 대신에 Perl 코드에서 유니코드를 사용하려면 소스 인코딩을 utf8으로 하고 use utf8; 프래그마를 사용해야 한다.
다음은 테스트하기 위해 만든 Java와 Perl 코드이다.
<Java>
public class Test {
public static void main(String[] args) {
String a = "abcdefghij가나다라마바사아자차";
StringBuilder sb = new StringBuilder();
for (int i=0; i<1000000; i++ ) {
sb.append(a);
}
String ab = sb.toString();
long sTime, eTime;
sTime = System.currentTimeMillis();
ab = ab.replaceAll("[^a-z]+", "_");
eTime = System.currentTimeMillis();
System.out.println(((eTime-sTime)/1000.)+ " sec");
}
}
<Perl>
#!/usr/bin/perl
use strict;
use warnings;
use Time::HiRes qw/gettimeofday tv_interval/;
use utf8;
my $a = "abcdefghij가나다라마바사아자차";
my $b = $a x 1000000;
my $sTime = [gettimeofday];
$b =~ s/[^a-z]+/_/g;
my $eTime = [gettimeofday];
print tv_interval($sTime, $eTime)," sec\n";
다음은 위 코드들로 3번씩 실행한 결과다.
<Java>
1.392 sec
1.441 sec
1.376 sec
<Perl>
1.564731 sec
1.531707 sec
1.511697 sec
결과를 보면 그렇게 차이가 많이 나지 않는다. use encoding 프래그마는 설계상의 문제점도 있지만 Perl의 내부 I/O서브 시스템을 변경하기 때문에 문자열 처리작업시 부하가 가중된다. 그래서 이것을 use utf8으로 바꾸고 하니 훨씬 빨라졌다.
Java가 Perl보다 빠른 이유는 Java는 내부적으로 문자열을 UTF-16인코딩으로 다루는데 이 인코딩 방법은 surrogate pair가 나오지 않는 이상 기본적으로 16bit(2byte)고정폭 인코딩이므로 문자열 치환 같은 작업 시 고정된 폭으로 메모리상 포인터를 이동하며 빠르게 작업 가능하기 때문이 아닌가 생각된다. 반면 Perl은 유니코드 사용 시 내부인코딩으로 UTF-8을 사용한다. UTF-8은 가변길이 인코딩으로 문자열에서 어떤 포인터를 이동시켜 나가면서 작업하려면 고정폭류 인코딩에 비해서 불리할수 밖에 없기 때문에 성능이 다소 떨어진 것으로 보인다.
그리고 Java가 Perl보다 메모리를 더 많이 사용한다고 하는데 그건 UTF-8인코딩에서 1byte로 표현되는 ASCII 알파벳 문자까지 JAVA는 UTF-16 2byte로 표현하고 문자열을 객체화시켜 다루기 때문에 Java가 더 메모리를 많이 사용할 수밖에 없을 것으로 보인다.
그런데 위 Perl코드를 다시 생각해보면 알파벳이 아닌 문자열을 _로 바꾸는 것이기 때문에 utf8인코딩 문자열에서는 그냥 문자열을 문자당 가변폭 인코딩이 아닌 단순 바이트시퀀스(byte sequence)로 보고 작업을 해도 상관없다. 그렇게 작업하면 일반 ASCII문자열 처럼 1byte고정폭으로 포인터를 이동시켜가며 작업할 것이기 때문에 자바에서 처럼 성능향상을 기대할 수 있을 것이라 추측하고 한 번 use utf8; 프래그마를 빼고 테스트 해보았다.
<결과>
1.060795 sec
1.06102 sec
1.0578 sec
역시나 예상대로 성능의 향상이 있었는데 Java보다 더 빨라졌다.
이제 Perl의 완승(?)
끝으로 Java하는 사람이 Perl의 특징을 이해하고 언어적 시각을 넓히는데 참고할 만한 글을 하나 소개할까 한다.
Why Michael Schwern is not a Java programmer.
일본에서 유명한 IT관련 잡지로 기술평론사의 WEB+DB PRESS 라는 격월로 간행되는 잡지가 있다. 여기서는 가끔 잡지에 연재된 내용을 모으고 약간의 내용을 추가하여 주제별 단행본을 내놓곤 하는데(예 - Perl 관련 단행본) 일본에 있는 지인으로부터 Naoya Ito씨가 주축이 되어서 쓴 시스템/인프라 관리 및 운영에 대한 책이 나올 것이라는 소식을 듣고 기대하고 있다가 나오자마자 부탁하여 기념사인회 장에서 Naoya Ito씨의 친필 싸인이 된 따끈따끈한 책을 인편을 통해 전해 받았었다..
<일본에서 출판된 원본 책 표지>
Naoya Ito씨는 아시아지역 Perl 컨퍼런스인 YAPC::Asia에서 매년 발표를 할 정도로 일본에서 유명한 Perl 해커 중 한 명으로 현재 뛰어난 기술력으로 일본의 작은 구글이라고 불리는 Hatena사에서 엔지니어로 근무하고 있다.(Hatena사는 일본의 최대 SNS싸이트인 Mixi와 함께 Perl을 기반으로 서비스를 구축하고 있는 대표적 회사 중 하나이기도 하다.)
Naoya Ito씨의 각종 발표자료
이 책을 그렇게 기다리며 구하여 보게 된 이유도 그가 각종 발표와 자료에서 보여준 시스템 커널레벨에서 어플리케이션 레벨에 이르는 개발능력과 시스템/인프라에 대한 폭넓은 지식과 시각을 갖춘 그의 식견과 노하우가 고스란히 녹아들어가 있을 것이라 기대했기 때문이다. 책을 받아 본 순간 역시나 기대를 져버리지 않았다 본인이 일본어를 못해서 한자와 그림 그리고 스크립트 코드를 통해서 맥락을 짚어가며 읽어 나갔지만, 그것으로도 신선하고 재미있었다. 만약 일본어를 할 줄 안다면 직접 번역해보고 싶었으나 그런 여건이 아니므로 혼자 몰래(?)보고 있었는데 오늘 서점에 들렀다가 우연히 번역서가 나온 것을 보고 반가운 마음에 당장 사버렸다.
<24시간365일 서버/인프라를 지탱하는 기술>
책의 저자는 Naoya Ito씨 뿐만 아니라 그 외 일본에서 내놓으라 하는 엔지니어 5명으로 이루어져 있으며 역할을 분담해서 내용을 알차게 구성하였다.
책의 내용은 오픈소스를 이용해서 어떻게 확장성/가용성 있는 인프라를 구축하느냐를 다루고 있으며 IPVS, LVS, DSR, Apache, Squid, Memcached, Nagio, Gangila, Puppet, MySQL등 인프라에 관심이 있는 사람이면 한 번 쯤은 들어봤을 만한 것들을 골고루 언급하고 있다.
세계최대의 인터넷 기업 구글도 그렇지만 일본의 선두 인터넷 기업들도 보면 공통점은 뛰어난 자체적 기술력을 보유하고 오픈소스를 활용하여 자신들만의 견고한 시스템을 구축했다는 공통점이 있다.
이 책은 그런 기업의 뛰어난 엔지니어들을 닮고자 하는 엔지니어들에게 좋은 길잡이가 되어 줄 수 있을 것이다.
<일본에서 출판된 원본 책 표지>
Naoya Ito씨는 아시아지역 Perl 컨퍼런스인 YAPC::Asia에서 매년 발표를 할 정도로 일본에서 유명한 Perl 해커 중 한 명으로 현재 뛰어난 기술력으로 일본의 작은 구글이라고 불리는 Hatena사에서 엔지니어로 근무하고 있다.(Hatena사는 일본의 최대 SNS싸이트인 Mixi와 함께 Perl을 기반으로 서비스를 구축하고 있는 대표적 회사 중 하나이기도 하다.)
Naoya Ito씨의 각종 발표자료
이 책을 그렇게 기다리며 구하여 보게 된 이유도 그가 각종 발표와 자료에서 보여준 시스템 커널레벨에서 어플리케이션 레벨에 이르는 개발능력과 시스템/인프라에 대한 폭넓은 지식과 시각을 갖춘 그의 식견과 노하우가 고스란히 녹아들어가 있을 것이라 기대했기 때문이다. 책을 받아 본 순간 역시나 기대를 져버리지 않았다 본인이 일본어를 못해서 한자와 그림 그리고 스크립트 코드를 통해서 맥락을 짚어가며 읽어 나갔지만, 그것으로도 신선하고 재미있었다. 만약 일본어를 할 줄 안다면 직접 번역해보고 싶었으나 그런 여건이 아니므로 혼자 몰래(?)보고 있었는데 오늘 서점에 들렀다가 우연히 번역서가 나온 것을 보고 반가운 마음에 당장 사버렸다.
<24시간365일 서버/인프라를 지탱하는 기술>
책의 저자는 Naoya Ito씨 뿐만 아니라 그 외 일본에서 내놓으라 하는 엔지니어 5명으로 이루어져 있으며 역할을 분담해서 내용을 알차게 구성하였다.
책의 내용은 오픈소스를 이용해서 어떻게 확장성/가용성 있는 인프라를 구축하느냐를 다루고 있으며 IPVS, LVS, DSR, Apache, Squid, Memcached, Nagio, Gangila, Puppet, MySQL등 인프라에 관심이 있는 사람이면 한 번 쯤은 들어봤을 만한 것들을 골고루 언급하고 있다.
세계최대의 인터넷 기업 구글도 그렇지만 일본의 선두 인터넷 기업들도 보면 공통점은 뛰어난 자체적 기술력을 보유하고 오픈소스를 활용하여 자신들만의 견고한 시스템을 구축했다는 공통점이 있다.
이 책은 그런 기업의 뛰어난 엔지니어들을 닮고자 하는 엔지니어들에게 좋은 길잡이가 되어 줄 수 있을 것이다.
Perl을 사용하면서 CPAN모듈을 제대로 쓰지 못한다면 앙꼬없는 찐빵과 같다. 하지만 CPAN모듈을 설치하려면 root권한이 없으면 시스템 전체에 적용되도록 모듈을 설치할 수 없고 일반 계정에 설치하려고 하면 모듈을 인스톨할 때 쓰이는 CPAN모듈과 부수적인 필요 모듈의 버젼에 따라 설정방법이 제각각이고 여러 가지 신경 써야 될 것이 많아서 그 또한 간단하지 않다.
그런데 이런 복잡한 혼돈(chaos)상태에서 한 방에 깔끔한 해결책을 내놓은 모듈이 나왔다. 그 이름은 local::lib 모듈 ! ! !
이제 인터넷을 검색하면 나오는 제각각의 방법인 "일반 사용자 계정에서 CPAN모듈 설치하기" 같은 문서들은 잊어버려도 된다.(그런 문서 중에는 잘못된 정보를 제공하는 것도 많다.)
local::lib 모듈은 사용자 계정에 독립적으로 자신만의 모듈을 추가로 설치해서 사용할 수 있도록 해주며 perl이 실행될 때 모듈을 찾기 위한 환경변수들 까지 자동으로 설정해주므로 이것저것 복잡하게 설정하고 신경 쓸 필요도 없으며 시스템의 상태를 건드리지 않고 깔끔하게 독립된 환경을 보장해 준다.
local::lib이 root에 의해 시스템 Perl 모듈라이브러리 경로에 설치되었다면 일반 계정 사용자가 바로 사용 가능하겠지만 그런 환경이 안되는 일반사용자라면 local::lib은 또 어떻게 설치해야 할까?
local::lib 모듈은 그런 상황에서도 바로 일반 계정 사용자가 bootstrapping 해서 설치할 수 있는 방법도 제공하고 있다.
그럼 일반 사용자 계정에서 독립적인 CPAN 모듈 인스톨/사용 환경을 구축해보자.
(계정명은 account로 가정)
http://search.cpan.org/dist/local-lib/ 에 가서 Download 링크를 눌러 파일을 받아서 일반 사용자 계정에 복사한다.
압축을 푼다.
tar zxvf local-lib-1.003003.tar.gz
압축을 푼 디렉토리에 들어간다.
cd local-lib-1.003003/
다음 명령을 내린다. (기본 모듈설치 경로를 바꾸고 싶으면 추가로 지정도 가능하다. - 자세한 건 모듈문서 참고 )
perl Makefile.PL --bootstrap
뭐라고 물어보는 프로프트가 뜨면 그냥 엔터를 친다.
그 다음 다음과 같이 차례로 명령내린다.
make test
make install
bash를 사용할 경우 다음 명령으로 .bashrc에 환경변수를 자동으로 세팅하는 명령을 추가한다. ( csh은 좀 다른데 이건 모듈 문서 참고 )
echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc
다음 명령으로 .bashrc를 다시 읽어들인다.(로그아웃후 다시 로그인해도 된다.)
source ~/.bashrc
이제 끝이다. 이제 cpan 명령으로 cpan을 실행시키고 임의의 모듈을 설치해보자
cpan>install Date::Korean
cpan을 빠져나와 실제로 모듈이 제대로 설치되었는지 테스트해보자.
perl -e 'use Date::Korean'
에러가 나지 않는다면 제대로 설치된 것이다.
local::lib은 별도로 경로를 지정해주지 않으면 기본으로 모든 모듈을 사용자 계정하의 perl5 디렉토리에 설치한다.
그리고 위에서 .bashrc에 추가한 줄은 실제적으로 perl -Mlocal::lib 라고 명령내리면 나오는
export MODULEBUILDRC="/home/account/perl5/.modulebuildrc"
export PERL_MM_OPT="INSTALL_BASE=/home/account/perl5"
export PERL5LIB="/home/account/perl5/lib/perl5:/home/account/perl5/lib/perl5/i486-linux-gnu-thread-multi:$PERL5LIB"
export PATH="/home/account/perl5/bin:$PATH"
와 같은 환경변수들을 자동으로 세팅해준다. 그래서 우리는 별다르게 신경쓰지 않고도 독립된 CPAN환경을 구축할 수 있는 것이다.
이제 CPAN모듈 설치는 local::lib 모듈로 한 방에 깔끔하게 끝내자!
더 자세한 사항은 모듈문서( http://search.cpan.org/perldoc?local::lib ) 과
Installing Perl modules as a non-root user 을 참고
그런데 이런 복잡한 혼돈(chaos)상태에서 한 방에 깔끔한 해결책을 내놓은 모듈이 나왔다. 그 이름은 local::lib 모듈 ! ! !
이제 인터넷을 검색하면 나오는 제각각의 방법인 "일반 사용자 계정에서 CPAN모듈 설치하기" 같은 문서들은 잊어버려도 된다.(그런 문서 중에는 잘못된 정보를 제공하는 것도 많다.)
local::lib 모듈은 사용자 계정에 독립적으로 자신만의 모듈을 추가로 설치해서 사용할 수 있도록 해주며 perl이 실행될 때 모듈을 찾기 위한 환경변수들 까지 자동으로 설정해주므로 이것저것 복잡하게 설정하고 신경 쓸 필요도 없으며 시스템의 상태를 건드리지 않고 깔끔하게 독립된 환경을 보장해 준다.
local::lib이 root에 의해 시스템 Perl 모듈라이브러리 경로에 설치되었다면 일반 계정 사용자가 바로 사용 가능하겠지만 그런 환경이 안되는 일반사용자라면 local::lib은 또 어떻게 설치해야 할까?
local::lib 모듈은 그런 상황에서도 바로 일반 계정 사용자가 bootstrapping 해서 설치할 수 있는 방법도 제공하고 있다.
그럼 일반 사용자 계정에서 독립적인 CPAN 모듈 인스톨/사용 환경을 구축해보자.
(계정명은 account로 가정)
http://search.cpan.org/dist/local-lib/ 에 가서 Download 링크를 눌러 파일을 받아서 일반 사용자 계정에 복사한다.
압축을 푼다.
tar zxvf local-lib-1.003003.tar.gz
압축을 푼 디렉토리에 들어간다.
cd local-lib-1.003003/
다음 명령을 내린다. (기본 모듈설치 경로를 바꾸고 싶으면 추가로 지정도 가능하다. - 자세한 건 모듈문서 참고 )
perl Makefile.PL --bootstrap
뭐라고 물어보는 프로프트가 뜨면 그냥 엔터를 친다.
그 다음 다음과 같이 차례로 명령내린다.
make test
make install
bash를 사용할 경우 다음 명령으로 .bashrc에 환경변수를 자동으로 세팅하는 명령을 추가한다. ( csh은 좀 다른데 이건 모듈 문서 참고 )
echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc
다음 명령으로 .bashrc를 다시 읽어들인다.(로그아웃후 다시 로그인해도 된다.)
source ~/.bashrc
이제 끝이다. 이제 cpan 명령으로 cpan을 실행시키고 임의의 모듈을 설치해보자
cpan>install Date::Korean
cpan을 빠져나와 실제로 모듈이 제대로 설치되었는지 테스트해보자.
perl -e 'use Date::Korean'
에러가 나지 않는다면 제대로 설치된 것이다.
local::lib은 별도로 경로를 지정해주지 않으면 기본으로 모든 모듈을 사용자 계정하의 perl5 디렉토리에 설치한다.
그리고 위에서 .bashrc에 추가한 줄은 실제적으로 perl -Mlocal::lib 라고 명령내리면 나오는
export MODULEBUILDRC="/home/account/perl5/.modulebuildrc"
export PERL_MM_OPT="INSTALL_BASE=/home/account/perl5"
export PERL5LIB="/home/account/perl5/lib/perl5:/home/account/perl5/lib/perl5/i486-linux-gnu-thread-multi:$PERL5LIB"
export PATH="/home/account/perl5/bin:$PATH"
와 같은 환경변수들을 자동으로 세팅해준다. 그래서 우리는 별다르게 신경쓰지 않고도 독립된 CPAN환경을 구축할 수 있는 것이다.
이제 CPAN모듈 설치는 local::lib 모듈로 한 방에 깔끔하게 끝내자!
더 자세한 사항은 모듈문서( http://search.cpan.org/perldoc?local::lib ) 과
Installing Perl modules as a non-root user 을 참고
인터넷을 돌아다니다가 http://isitruby19.com/ 라는 싸이트를 발견했다. 이곳은 기존 Ruby 모듈들이 새로운 Ruby버젼인 1.9에서 동작하는지를 사용자가 테스트해서 결과를 댓글형식으로 올리면 카운트해서 보여주는 싸이트라고 하는데 이런 발상이 나왔다는 자체가 Perl CPAN을 사용하는 입장에서는 이해하기 힘들었다.
Perl모듈 저장소인 CPAN은 모듈 인스톨시 자동으로 빌드,설치 성공/실패여부를 정교하게 채크하고 리포팅하는 테스팅인프라를 가지고 있다.
일례로 비슷한 성격의 모듈인 Ruby의 rcov와 Perl의 Devel::Cover 모듈의 테스트를 비교해보자.
Ruby - rcov http://isitruby19.com/rcov
Perl - Devel::Cover http://www.cpantesters.org/show/Devel-Cover.html
http://cpants.perl.org/dist/overview/Devel-Cover
어떤 차이점이 느껴지는가?
Perl은 20년도 지난 1987년 나온 Perl 1이 이미 328 core test case를 가질 정도로 근본적으로 견고한 테스팅문화를 가졌으며 그것을 기반으로 오늘날 어떤 언어도 이루지 못한 규모의 모듈저장소인 CPAN과 자동화된 각종 테스트인프라를 가지게 되었다. 둘간의 접근방식의 차이는 그런 근본적 문화적(?) 차이에 기인하지 않나 생각된다.
일례로 Ruby 1.8.6의 core test case가 867개인 반면 Perl 5의 core test case는 Ruby의 75배 정도인 64793개 라고 한다. ( http://www.oreillynet.com/onlamp/blog/2007/05/trust_but_verify.html ) [참고] Python 2.5.1의 test case(core에다가 표준 라이브러리 테스트도 포함)는 Perl의 1/5정도인 11437개라고 한다.
Perl의 이런 근본적으로 견고한 테스트문화는 Parrot과 Perl 6에서도 그대로 이어지고 있다.
Perl모듈 저장소인 CPAN은 모듈 인스톨시 자동으로 빌드,설치 성공/실패여부를 정교하게 채크하고 리포팅하는 테스팅인프라를 가지고 있다.
일례로 비슷한 성격의 모듈인 Ruby의 rcov와 Perl의 Devel::Cover 모듈의 테스트를 비교해보자.
Ruby - rcov http://isitruby19.com/rcov
Perl - Devel::Cover http://www.cpantesters.org/show/Devel-Cover.html
http://cpants.perl.org/dist/overview/Devel-Cover
어떤 차이점이 느껴지는가?
Perl은 20년도 지난 1987년 나온 Perl 1이 이미 328 core test case를 가질 정도로 근본적으로 견고한 테스팅문화를 가졌으며 그것을 기반으로 오늘날 어떤 언어도 이루지 못한 규모의 모듈저장소인 CPAN과 자동화된 각종 테스트인프라를 가지게 되었다. 둘간의 접근방식의 차이는 그런 근본적 문화적(?) 차이에 기인하지 않나 생각된다.
일례로 Ruby 1.8.6의 core test case가 867개인 반면 Perl 5의 core test case는 Ruby의 75배 정도인 64793개 라고 한다. ( http://www.oreillynet.com/onlamp/blog/2007/05/trust_but_verify.html ) [참고] Python 2.5.1의 test case(core에다가 표준 라이브러리 테스트도 포함)는 Perl의 1/5정도인 11437개라고 한다.
Perl의 이런 근본적으로 견고한 테스트문화는 Parrot과 Perl 6에서도 그대로 이어지고 있다.
얼마 전 열린 (OSDC.tw 2009) Open Source Developers' Conference Taiwan의 발표자료들
자료모음 링크
이 중에 관심 가는 자료 몇 개를 골라보자면
SD, a P2P bug tracking system
Perl 5.10 on OSDC.tw 2009
Good Evils In Perl
Remedie OSDC.TW
Trading With Open Source Tools
자료모음 링크
이 중에 관심 가는 자료 몇 개를 골라보자면
SD, a P2P bug tracking system
Perl 5.10 on OSDC.tw 2009
Good Evils In Perl
Remedie OSDC.TW
Trading With Open Source Tools
JEEN님이 369게임에 관한 문제( http://cafe.naver.com/perlstudy.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=150 )를 올리셨기에 심심해서 Perl로 해봤다.
369게임의 규칙은 간단하다.
숫자 중에 369가 있으면 나온 횟수만큼 박수를 치고 하나도 없다면 숫자 그대로 말하면 된다.
6 -> clap
306 -> clap clap
400 -> 400
그럼 이것을 어떤 숫자를 넘기면 결과를 넘겨주는 함수 형태(이름은 f)로 만들어 보자.
누가 비슷한 코딩을 한 적이 없나 인터넷을 검색해보니 Ruby로 구현한 분이 계셨다.
(코드골프는 코드를 최대한 짧게 만드는 게임인데 Perl이 거의 상위권을 휩쓸고 있다. 짧게 만들면 그만큼 함축적인 의미가 많이 들어가므로 가독성은 떨어진다. 하지만 아이러니한건 코드골프에나 쓰일 것 같은 코드를 예를 들며 Perl이 가독성이 안 좋다고 종종 흠잡곤 하는 타 언어(?) 사용자들도 자신이 쓰는 언어의 간결함을 뽐내려고 짧게 줄인 코드를 내보이며 "내가 쓰는 언어는 강력해!" 하고 으스댄다는 사실... 번데기앞에서 주름잡는 꼴이라고나 할까?)
sub f{($_)=@_;"clap "x y/369//||$_[0]}
<검증>
$perl -le 'print f(6); sub f{($_)=@_;"clap "x y/369//||$_[0]}'
clap
$perl -le 'print f(306); sub f{($_)=@_;"clap "x y/369//||$_[0]}'
clap clap
$perl -le 'print f(400); sub f{($_)=@_;"clap "x y/369//||$_[0]}'
400
함수 f를 좀 늘여서 쓰면 다음과 같이 쓸 수 있다.
sub f {
my ($num)=@_;
my $count = $num =~ tr/369//;
if ( $count == 0 ) { return $num }
return "clap " x $count;
}
이것이 어떻게 저렇게 짧게 줄어들 수 있는지는
1. $_ 변수
2. tr(y)연산이 대상이 지정되지 않으면 기본으로 $_에 대해 동작
3. tr(y)연산의 결과는 교체된 문자의 개수를 리턴한다.
4. x는 문자열의 반복횟수를 지정하는 연산자이다.
5. Perl 함수(서브루틴은) 명시적으로 return하지 않으면 마지막으로 평가된 표현식의 결과를 리턴한다.
정도를 알면 별 무리 없이 해석 가능할 것이다.
369게임의 규칙은 간단하다.
숫자 중에 369가 있으면 나온 횟수만큼 박수를 치고 하나도 없다면 숫자 그대로 말하면 된다.
6 -> clap
306 -> clap clap
400 -> 400
그럼 이것을 어떤 숫자를 넘기면 결과를 넘겨주는 함수 형태(이름은 f)로 만들어 보자.
누가 비슷한 코딩을 한 적이 없나 인터넷을 검색해보니 Ruby로 구현한 분이 계셨다.
Ruby로 짜본 간단한 369게임
일단 코드골프 식으로 최대한 짧게 줄이고 줄인 모양은 다음과 같다.(코드골프는 코드를 최대한 짧게 만드는 게임인데 Perl이 거의 상위권을 휩쓸고 있다. 짧게 만들면 그만큼 함축적인 의미가 많이 들어가므로 가독성은 떨어진다. 하지만 아이러니한건 코드골프에나 쓰일 것 같은 코드를 예를 들며 Perl이 가독성이 안 좋다고 종종 흠잡곤 하는 타 언어(?) 사용자들도 자신이 쓰는 언어의 간결함을 뽐내려고 짧게 줄인 코드를 내보이며 "내가 쓰는 언어는 강력해!" 하고 으스댄다는 사실... 번데기앞에서 주름잡는 꼴이라고나 할까?)
sub f{($_)=@_;"clap "x y/369//||$_[0]}
<검증>
$perl -le 'print f(6); sub f{($_)=@_;"clap "x y/369//||$_[0]}'
clap
$perl -le 'print f(306); sub f{($_)=@_;"clap "x y/369//||$_[0]}'
clap clap
$perl -le 'print f(400); sub f{($_)=@_;"clap "x y/369//||$_[0]}'
400
함수 f를 좀 늘여서 쓰면 다음과 같이 쓸 수 있다.
sub f {
my ($num)=@_;
my $count = $num =~ tr/369//;
if ( $count == 0 ) { return $num }
return "clap " x $count;
}
이것이 어떻게 저렇게 짧게 줄어들 수 있는지는
1. $_ 변수
2. tr(y)연산이 대상이 지정되지 않으면 기본으로 $_에 대해 동작
3. tr(y)연산의 결과는 교체된 문자의 개수를 리턴한다.
4. x는 문자열의 반복횟수를 지정하는 연산자이다.
5. Perl 함수(서브루틴은) 명시적으로 return하지 않으면 마지막으로 평가된 표현식의 결과를 리턴한다.
정도를 알면 별 무리 없이 해석 가능할 것이다.
Test Center: Intel's Nehalem simply sizzles
- 네할렘 아키텍쳐로 반격에 나선 Intel
ext4 File System: Introduction and Benchmarks
- ext4가 성능이 떨어진다는 소문이 있던데 이제 괜찮아진 듯, 슬슬 갈아탈 때?
What is the official branch of MySQL?
- MySQL 창시자가 Sun에서 나와 만든 MySQL fork MariaDB 거기에 또 다른 MySQL fork Drizzle 누가누가 잘하나? 이기는 편 우리 편~
- 네할렘 아키텍쳐로 반격에 나선 Intel
ext4 File System: Introduction and Benchmarks
- ext4가 성능이 떨어진다는 소문이 있던데 이제 괜찮아진 듯, 슬슬 갈아탈 때?
What is the official branch of MySQL?
- MySQL 창시자가 Sun에서 나와 만든 MySQL fork MariaDB 거기에 또 다른 MySQL fork Drizzle 누가누가 잘하나? 이기는 편 우리 편~

