[gypark@www perlstudy_1473]$ ./sort.pl 11
method 1 - in-memory sort. [input_seq.txt]
laptime time after loading file : 1.730617
laptime time after counting : 4.001903
laptime time after sorting : 13.373383
method 1 - save [sorted_1_seq.txt]
total elapsed time : 14.21391
사용메모리: 약 745M
랜덤 파일
[gypark@www perlstudy_1473]$ ./sort.pl 12
method 1 - in-memory sort. [input_rnd.txt]
laptime time after loading file : 1.725655
laptime time after counting : 4.212954
laptime time after sorting : 40.480208
method 1 - save [sorted_1_rnd.txt]
total elapsed time : 41.319748
사용메모리: 약 820M
파일을 읽어 배열에 넣는데 1.7초, 그 배열원소 3백만개를 순회하며 키를 뽑아내어 빈도수를 재는 데 2.3초 정도 걸리는 건 비슷하나, 그 다음 정렬할 때 차이가 많이 난다. 순차 파일의 경우 같은 키에 해당하는 원소들이 이미 정렬된 채로 모여 있어서 그런 듯.
(여담으로, 위 테스트를 처음에는 윈도우7이 깔린 컴퓨터에서 하려고 했는데, 파일을 읽는 데에만 십수분이 걸려도 끝나지 않았다 -_-;; /StringConcatenationBenchmark에서도 얘기가 나왔었지만 윈도우에서 메모리를 realloc 으로 계속 늘려나갈 때 문제가 있어서 그런 게 아닐까 짐작함)
문제는 메모리 사용량이다. 25MB 짜리 텍스트 파일을 읽어 배열에 저장할 때 400MB 이상을 쓰게되고, 정렬을 위해서 배열이 하나 복사되면서 두 배가 된다. 라인 수를 7백만개로 늘렸을 경우 내 테스트 환경에서는 순차 파일은 2GB를 간당간당하게 쓰면서 40초 정도 끝났는데, 랜덤 파일은 디스크에 스왑을 하게되는 듯 끝날 기미가 안 보여서 포기했음.
메모리가 부족해서 통채로 읽을 수 없으니, 할 수 없이 디스크를 쓰기로 한다.
기본 컨셉은, 하나의 키에 해당하는 라인들만 뽑아내어 파일 하나에 저장하는 식으로 분리를 한 후, 빈도수가 높은 (즉 라인수가 많은) 키의 파일들부터 읽어서 하나의 파일에 기록하는 식.
my%freq = ();
openmy$in, '<', $input_file;
while ( my$line = <$in> ) {
my$key = (split /\s+/, $line)[0];
$freq{$key}++;
# 한 라인을 읽을 때마다 매번 그 라인에 해당하는 키 파일을 열고 거기에 덧붙인 후 닫음openmy$tmp, '>>', $key;
print {$tmp} $line;
close$tmp;
}
close$in;
openmy$out, ">", $output_file;
foreachmy$key ( sort { $freq{$b} <=> $freq{$a} or$a <=> $b } keys%freq ) {
# 정렬한 키 순서대로 키 파일을 읽어서 최종 결과물에 덧붙임openmy$tmp, '<', $key;
while ( my$line = <$tmp> ) {
print {$out} $line;
}
close$tmp;
unlink$key;
}
close$out;
실행 결과:
[gypark@www perlstudy_1473]$ ./sort.pl 21
method 2 - open-close in each iteration. [input_seq.txt]
laptime time after loading, counting, splitting : 132.328214
method 2 - save [sorted_2_seq.txt]
total elapsed time : 134.268979
사용메모리: 약 10M
[gypark@www perlstudy_1473]$ ./sort.pl 22
method 2 - open-close in each iteration. [input_rnd.txt]
laptime time after loading, counting, splitting : 1081.486577
method 2 - save [sorted_2_rnd.txt]
total elapsed time : 1089.66655
사용메모리: 약 10M
메모리는, 쓸래야 쓸 일이 없다. 항상 한 라인씩 처리하기 때문에 10MB를 넘지 않는다.
순차 파일과 랜덤 파일 둘 다 정렬하고 최종 파일에 기록하는 건 몇 초 걸리지 않는데, 입력을 읽으며 키 파일들을 만드는 데 시간의 차이가 크게 난다. 어차피 3백만 번 오픈,클로즈를 꼬박꼬박 하는 건 마찬가지인데 이렇게 차이가 나는 건, 순차 파일의 경우는 한번 열었던 파일을 다시 열게 되니까 이 때는 버퍼 캐시 등의 도움으로 오버헤드가 줄어들기 때문으로 생각됨.
3백만 라인을 읽는 동안 매번 파일을 열고,닫는 건 너무 오버헤드가 크다. 이걸 줄여볼 요량으로, 어떤 키가 처음 나타날 때 그 키에 해당하는 파일을 열고, 그 다음부터는 계속 연 채로 두기로 한다.
my%freq = ();
# 각 키에 해당하는 파일 핸들들의 해시my%fh = ();
openmy$in, '<', $input_file;
while ( my$line = <$in> ) {
my$key = (split /\s+/, $line)[0];
$freq{$key}++;
# 어떤 키가 처음 나오면 그 때 열고if ( notexists$fh{$key} ) {
open$fh{$key}, '>', $key;
}
# 열린 파일 핸들을 계속 사용print {$fh{$key}} $line;
}
close$in;
# 나머지는 앞 섹션과 동일
실행 결과:
[gypark@www perlstudy_1473]$ ./sort.pl 31
method 3 - open-close once. [input_seq.txt]
Failed to load 'autodie::exception'.
This may be a typo in the 'autodie->exception_class' method,
or the 'autodie::exception' module may not exist.
Can't locate autodie/exception.pm: Too many open files at (eval 105) line 2, <$_[...]> line 1917511. at /home/gypark/perl5/perlbrew/perls/perl-5.14.2/lib/5.14.2/Fatal.pm line 1250, <$_[...]> line 1917511. Fatal::throw('autodie', 'function', 'CORE::open', 'args', 'ARRAY(0xa0997f8)', 'pragma', 'autodie', 'errno', 'Too many open files', ...) called at (eval 19) line 114 main::__ANON__('GLOB(0xa0997b8)', '>', 1020) called at ./sort.pl line 108
안타깝게도... 시스템에서 한 프로세스가 동시에 열 수 있는 파일의 갯수에는 제한이 있다. 이 시스템에서는 1024로 설정되어 있고, 수정하려면 /etc/security/limits.conf에서 해 줄 수 있긴 하다. 윈도우에서는 2048개 정도인 듯 한데 수정하는 방법을 알아내지 못 했다. (구글링해보면 말이 다 다름)
한군님이 데이타 사이즈를 줄여서 테스트한 게 있는데, 거기에서는 매번 오픈-클로즈 할 때에 비해 속도가 절반으로 줄어들었다고 한다.