Perl/Archive-Zip 페이지의 소스 보기
마지막으로 [b]
-- Loading page list... --
내용출력
로그인[l]
Diary
[f]
최근변경내역
[r]
페이지목록[i]
횡설수설[2]
게시판[3]
링크
수정할 수 없습니다: Perl/Archive-Zip 는 읽기 전용 페이지입니다.
Cpan:Archive::Zip Zip 압축파일을 만들거나 풀 때 사용하는 모듈.
== # 반복적인 read() 호출과 extract() 관련 문제점 == [http://advent.perl.kr/2013/2013-12-20.html 스무번째 날: Gearman 사용 사례 -- 펄 크리스마스 달력 #2013] 기사를 작성하는 도중에 발견. * 하나의 Archive::Zip 오브젝트에 대하여
read
메쏘드를 호출하여 압축 파일을 읽을 때마다 점점 더 메모리를 차지하게 되고 * 이것이 영향을 미치는 걸로 보이는데,
extractTree
또는
extractMemberWithoutMethods
메쏘드를 호출하여 내용을 추출할 때 점점 더 많은 시간을 소모하게 된다. * 따라서 수천 회 이상 압축 파일을 다루게 될 경우 속도가 눈에 띄게 저하될 수 있으므로 Archive::Zip 오브젝트를 재사용하지 말고 새로 생성하여 사용할 것을 권장함 === # 실험1 === 일단 한 번만 read()하고, 루프를 돌며 2만번 extract()하는 테스트. {{{#!vim perl # Archive::Zip 오브젝트 생성 my $zip = Archive::Zip->new(); # 압축 파일을 한번만 읽고 $zip->read($file) == AZ_OK; for my $num ( 1 .. 20000 ) { # 특정 파일을 추출을 2만번 반복 $zip->extractMemberWithoutPaths($member); } }}}
{{{#!vim perl !/usr/bin/env perl use strict; use warnings; # local $| = 1; use Archive::Zip qw/:ERROR_CODES :CONSTANTS/; use Time::HiRes qw/gettimeofday tv_interval/; use Memory::Usage; my $member = 'LIST.txt'; # ZIP 파일 내에서 꺼낼 파일 my $file = '/home/gypark/temp/gearman/data/00001.zip'; my $mu = Memory::Usage->new; # Archive::Zip 오브젝트 생성 my $zip = Archive::Zip->new(); my $time_read = 0; my $time_extract = 0; my $t0; my $elapsed; # 압축 파일을 한번만 읽고 die 'read erorr' unless ( $zip->read($file) == AZ_OK ); $mu->record('before loop'); for my $num ( 1 .. 20000 ) { # 특정 파일을 추출을 2만번 반복 $t0 = [ gettimeofday ]; $zip->extractMemberWithoutPaths($member); $elapsed = tv_interval($t0); $time_extract += $elapsed; if ( $num % 1000 == 0 ) { printf("%d, %.6f, %.6f\n", $num, $time_read/1000, $time_extract/1000); $time_read = 0; $time_extract = 0; } # 추출했던 파일은 삭제 unlink $member or die "unlink:$!"; } $mu->record('after loop'); $mu->dump(); }}}
결과: {{{ 1000, 0.000000, 0.000742 <-- 매 천 번 반복하는 동안 read()와 extract..()의 평균 실행 시간 2000, 0.000000, 0.000750 3000, 0.000000, 0.000746 4000, 0.000000, 0.000768 5000, 0.000000, 0.000772 6000, 0.000000, 0.000771 7000, 0.000000, 0.000756 8000, 0.000000, 0.000649 9000, 0.000000, 0.000626 10000, 0.000000, 0.000754 11000, 0.000000, 0.000764 12000, 0.000000, 0.000764 13000, 0.000000, 0.000583 14000, 0.000000, 0.000658 15000, 0.000000, 0.000495 16000, 0.000000, 0.000514 17000, 0.000000, 0.000608 18000, 0.000000, 0.000589 19000, 0.000000, 0.000708 20000, 0.000000, 0.000760 time vsz ( diff) rss ( diff) shared ( diff) code ( diff) data ( diff) 0 94868 ( 94868) 8920 ( 8920) 2068 ( 2068) 1412 ( 1412) 7136 ( 7136) before loop 14 94868 ( 0) 8968 ( 48) 2096 ( 28) 1412 ( 0) 7136 ( 0) after loop }}} * read()는 처음 한 번만 하고 루프 내에서는 실행하지 않았으니 시간이 0.0이고 * extractMemberWithoutPaths()의 평균 수행 시간은 루프를 천 번 반복하는 동안 합산한 후 1000으로 나눈 값이다. 즉 처음 천 번 루프를 도는 동안 이 함수를 한번 호출할 때 수행 시간은 평균 0.742밀리초였고, 그 다음 천 번 루프를 도는 동안은 평균 0.750밀리초...이런 식. 위 결과를 보면 '''처음 천 번 돌 때나 마지막 스무 번째 천 번 돌 때나 평균시간은 항상 0.8밀리초 이내를 유지'''한다. * 마지막 두 줄은 루프를 돌기 전과 후의 사용 메모리 변화인데, '''data size 에 전혀 변화가 없다'''. === # 실험2 === 이번에는 2만번 루프를 돌면서 매번 read()하고 extract()하는 테스트. {{{#!vim perl # Archive::Zip 오브젝트 생성 my $zip = Archive::Zip->new(); for my $num ( 1 .. 20000 ) { # 압축 파일을 매번 읽고 $zip->read($file); # 특정 파일을 추출 $zip->extractMemberWithoutPaths($member); } }}}
{{{#!vim perl #!/usr/bin/env perl use strict; use warnings; # local $| = 1; use Archive::Zip qw/:ERROR_CODES :CONSTANTS/; use Time::HiRes qw/gettimeofday tv_interval/; use Memory::Usage; my $member = 'LIST.txt'; # ZIP 파일 내에서 꺼낼 파일 my $file = '/home/gypark/temp/gearman/data/00001.zip'; my $mu = Memory::Usage->new; # Archive::Zip 오브젝트 생성 my $zip = Archive::Zip->new(); my $time_read = 0; my $time_extract = 0; my $t0; my $elapsed; $mu->record('before loop'); for my $num ( 1 .. 20000 ) { # 압축 파일을 매번 읽고 $t0 = [ gettimeofday ]; $zip->read($file); $elapsed = tv_interval($t0); $time_read += $elapsed; # 특정 파일을 추출 $t0 = [ gettimeofday ]; $zip->extractMemberWithoutPaths($member); $elapsed = tv_interval($t0); $time_extract += $elapsed; if ( $num % 1000 == 0 ) { printf("%d, %.6f, %.6f\n", $num, $time_read/1000, $time_extract/1000); $time_read = 0; $time_extract = 0; } # 추출했던 파일은 삭제 unlink $member or die "unlink:$!"; } $mu->record('after loop'); $mu->dump(); }}}
결과: {{{ 1000, 0.000462, 0.000902 2000, 0.000469, 0.001179 3000, 0.000461, 0.001391 4000, 0.000450, 0.001615 5000, 0.000409, 0.001711 6000, 0.000355, 0.001777 7000, 0.000351, 0.001891 8000, 0.000394, 0.002372 9000, 0.000403, 0.002621 10000, 0.000384, 0.002761 11000, 0.000334, 0.002657 12000, 0.000383, 0.003267 13000, 0.000389, 0.003607 14000, 0.000453, 0.004447 15000, 0.000339, 0.003767 16000, 0.000423, 0.004907 17000, 0.000402, 0.005094 18000, 0.000412, 0.005584 19000, 0.000437, 0.006368 20000, 0.000446, 0.006684 time vsz ( diff) rss ( diff) shared ( diff) code ( diff) data ( diff) 0 94872 ( 94872) 8884 ( 8884) 2036 ( 2036) 1412 ( 1412) 7140 ( 7140) before loop 74 173532 ( 78660) 87472 ( 78588) 2096 ( 60) 1412 ( 0) 85800 ( 78660) after loop }}} * 루프를 2만번 도는 동안 '''read()의 실행 시간은 항상 0.4밀리초 안팎으로 일정'''하다. * 그러나 '''extract 함수의 실행 시간은 점점 길어져서, 마지막에는 처음과 비교해서 거의 7~8배로 늘어난다'''. 그래서 스크립트 전체가 도는 시간도 실험1은 14초 정도였으나 실험2는 73초 정도 걸렸다. * 메모리 역시, '''루프를 돌고 나면 78.7MB 정도가 늘어나 있다'''. 루프 한 번 돌때마다 4KB 정도 늘어난 셈 Upload:extractMemberWithoutPaths.png \\ (실험1과 실험2에서 extract 함수의 실행시간 비교) === # 실험3 === 이건 확인 차원에서, 반대로 read 만 2만번 반복하게 한 것. {{{#!vim perl # Archive::Zip 오브젝트 생성 my $zip = Archive::Zip->new(); for my $num ( 1 .. 20000 ) { # 압축 파일을 매번 읽고, 추출은 하지 않음 $zip->read($file); } }}}
{{{#!vim perl #!/usr/bin/env perl use strict; use warnings; # local $| = 1; use Archive::Zip qw/:ERROR_CODES :CONSTANTS/; use Time::HiRes qw/gettimeofday tv_interval/; use Memory::Usage; my $member = 'LIST.txt'; # ZIP 파일 내에서 꺼낼 파일 my $file = '/home/gypark/temp/gearman/data/00001.zip'; my $mu = Memory::Usage->new; # Archive::Zip 오브젝트 생성 my $zip = Archive::Zip->new(); my $time_read = 0; my $time_extract = 0; my $t0; my $elapsed; $mu->record('before loop'); for my $num ( 1 .. 20000 ) { # 압축 파일을 매번 읽고, 추출은 하지 않음 $t0 = [ gettimeofday ]; $zip->read($file); $elapsed = tv_interval($t0); $time_read += $elapsed; if ( $num % 1000 == 0 ) { printf("%d, %.6f, %.6f\n", $num, $time_read/1000, $time_extract/1000); $time_read = 0; $time_extract = 0; } } $mu->record('after loop'); $mu->dump(); }}}
결과: {{{ 1000, 0.000449, 0.000000 2000, 0.000444, 0.000000 3000, 0.000442, 0.000000 4000, 0.000444, 0.000000 5000, 0.000442, 0.000000 6000, 0.000446, 0.000000 7000, 0.000443, 0.000000 8000, 0.000449, 0.000000 9000, 0.000446, 0.000000 10000, 0.000422, 0.000000 11000, 0.000355, 0.000000 12000, 0.000286, 0.000000 13000, 0.000285, 0.000000 14000, 0.000287, 0.000000 15000, 0.000286, 0.000000 16000, 0.000296, 0.000000 17000, 0.000303, 0.000000 18000, 0.000308, 0.000000 19000, 0.000327, 0.000000 20000, 0.000445, 0.000000 time vsz ( diff) rss ( diff) shared ( diff) code ( diff) data ( diff) 0 94868 ( 94868) 8880 ( 8880) 2036 ( 2036) 1412 ( 1412) 7136 ( 7136) before loop 8 171756 ( 76888) 85876 ( 76996) 2076 ( 40) 1412 ( 0) 84024 ( 76888) after loop }}} * read() 시간은 역시 항상 비슷비슷하다. * extract 함수는 호출하지 않았으니 볼 것 없고 * '''메모리 사용량이 실험2와 동일하게 늘었다'''. === # 실험4 - 매번 객체 만들기 === 생각해보니까, 정작 이 테스트를 빼먹었다. 루프를 2만 번 돌면서 매번 Archive::Zip 객체를 새로 생성해서 사용하자. {{{#!vim perl for my $num ( 1 .. 20000 ) { # Archive::Zip 오브젝트를 매번 생성 my $zip = Archive::Zip->new(); # 압축 파일을 매번 읽고 $zip->read($file); # 특정 파일을 추출 $zip->extractMemberWithoutPaths($member); } }}}
{{{#!vim perl #!/usr/bin/env perl use strict; use warnings; # local $| = 1; use Archive::Zip qw/:ERROR_CODES :CONSTANTS/; use Time::HiRes qw/gettimeofday tv_interval/; use Memory::Usage; my $member = 'LIST.txt'; # ZIP 파일 내에서 꺼낼 파일 my $file = '/home/gypark/temp/gearman/data/00001.zip'; my $mu = Memory::Usage->new; my $time_read = 0; my $time_extract = 0; my $t0; my $elapsed; $mu->record('before loop'); for my $num ( 1 .. 20000 ) { # Archive::Zip 오브젝트를 매번 생성 my $zip = Archive::Zip->new(); # 압축 파일을 매번 읽고 $t0 = [ gettimeofday ]; $zip->read($file); $elapsed = tv_interval($t0); $time_read += $elapsed; # 특정 파일을 추출 $t0 = [ gettimeofday ]; $zip->extractMemberWithoutPaths($member); $elapsed = tv_interval($t0); $time_extract += $elapsed; if ( $num % 1000 == 0 ) { printf("%d, %.6f, %.6f\n", $num, $time_read/1000, $time_extract/1000); $time_read = 0; $time_extract = 0; } # 추출했던 파일은 삭제 unlink $member or die "unlink:$!"; } $mu->record('after loop'); $mu->dump(); }}}
결과: {{{ 1000, 0.000419, 0.000705 2000, 0.000361, 0.000610 3000, 0.000412, 0.000692 4000, 0.000428, 0.000721 5000, 0.000457, 0.000770 6000, 0.000459, 0.000787 7000, 0.000475, 0.000797 8000, 0.000421, 0.000705 9000, 0.000428, 0.000721 10000, 0.000424, 0.000715 11000, 0.000418, 0.000706 12000, 0.000439, 0.000735 13000, 0.000388, 0.000636 14000, 0.000478, 0.000790 15000, 0.000456, 0.000777 16000, 0.000433, 0.000727 17000, 0.000425, 0.000721 18000, 0.000322, 0.000543 19000, 0.000392, 0.000655 20000, 0.000395, 0.000665 time vsz ( diff) rss ( diff) shared ( diff) code ( diff) data ( diff) 0 94872 ( 94872) 8884 ( 8884) 2036 ( 2036) 1412 ( 1412) 7140 ( 7140) before loop 24 94872 ( 0) 8976 ( 92) 2096 ( 60) 1412 ( 0) 7140 ( 0) after loop }}} * read() 시간도 일정 * extract() 시간도 일정 * 메모리도 별다른 누수는 없는 걸로 보임 * 매번 객체를 생성하는 오버헤드는 어쩔 수 없이 있겠지만, 전체 실행 시간을 생각하면 훨씬 이득이다. (24초 정도 걸렸음) === # 중간 결론 === * read()가 압축 파일을 읽을 때 기존에 읽었던 메모리를 제대로 반납하지 않는지, read()를 할 때마다 메모리를 차지함 * 게다가 압축 파일의 내용을 읽어 추출할 때 이렇게 늘어난 메모리의 양의 영향을 받는 걸로 보인다. * 테스트는 extractTree()와 extractMemberWithoutPaths() 두 가지만 가지고 했으나, 아마도 extract... 함수들 모두에 적용될 것으로 짐작됨. * 이제 증상은 파악했으니 모듈 소스를 들여다보고 문제점을 찾으면 좋겠으나... 그럴 의지도 시간도 없어서 버그 리포트나 해야겠다. 어쩌면 이미 리포트도 되어 있는지도 모르겠음. * '''아무튼 이 모듈을 쓰는 사람은 이것을 명심하여 다수의 압축 파일을 처리할 때는 매번 Archive::Zip 객체를 새로 만드는 게 좋겠다.''' == # 기타 & Comments ==
---- [[컴퓨터분류]]
Perl/Archive-Zip
페이지로 돌아가기 |
다른 수정본 보기