Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

소개

프로그램을 작성하다 보면, 성능/통계/테스트 측정 데이들이 쏟아져나오게 된다. 예를들어 필자의 경우 최근 검색엔진(:12)관련 개발을 하고 있는데, 0.5초 이내에 검색결과가 나오도록 해야 하기 때문에, 성능측정결과를 정리하는게 특히 중요하다.

검색엔진에서 단어를 입력하면, 해당 단어를 포함한 문서의 리스트를 찾아내게 될건데, 문서의 리스트의 증가에 따라서 얼마나 검색시간이 급격하게 증가하는지를 쉽게 확인할 수 있어야 한다. 그러기 위해서는 측정데이터를 그래프화 할 수 있어야 한다. 이렇게 측정데이터가 그래프화 되면 검색시간이 급격하게 증가하는 단어등도 쉽게 찾아낼 수 있으며, 여러가지 조건을 다르게 했을 때, 성능에 어떠한 변화가 있는지도 확인할 수 있다. 결과적으로 개발속도를 단축시킬 수 있게 된다.

GnuPlot은 데이터를 그래프화 하기 위한 목적으로 사용되는 툴이다. Excel과 같은 프로그램이 있긴 하지만, 지나치게 무겁고, (많은 데이터를 처리해야 할경우)매우 느리며, GnuPlot(:12)만큼 정확한 정보를 주지 못한다. 데이터가 수십만개가 넘어간다면, 처리자체가 불가능해지게 된다.

GnuPlot와 관련된 메뉴얼은 한글화 된거 까지 포함해서 이미 몇개가 있는데, 쓸데없이? 방대한 내용을 담고 있다. 여기에서는 좀더 개발자적인 입장에서, 실제 개발에서의 적용을 예를 들어가면서 활용에 필요한 부분만 설명하고자 한다.

gnuplot 설치

대부분 기본으로 설치되어 있을 것이다. 터미널에서 gnuplot을 입력해 보기 바란다. 만약 설치되어 있지 않다면, 각 배포판의 패키지 관리자나 배포사이트를 통해서 설치하기 바란다. 패키지 설치법은 따로 설명하지 않겠다.

검색엔진 개발과 GnuPlot

gnuplot의 활용설명을 위해서 nutch 성능측정을 통한 튜닝포인트 찾기를 실례로해서 차근차근 설명해 보도록 하겠다.

nutch(:12)는 lucene검색엔진을 기반으로 하고 있으며, 여기에 분산환경을 지원하기 위한 hadoop(:12)분산파일 시스템과 mapreduce(:12)프로그래밍 엔진을 포함하고 있다. lucene은 검색엔진 자체로만 보자면 매우 훌륭한 완성된 엔진이지만, 많은 공개프로젝트의 결과물이 그렇듯이 범용성을 강조하다보니, 특수한 상황에 적용하기 위해서는 많은 튜닝이 필요하다. 특히 대용량의 문서검색을 위해서 nutch를 사용할 경우 더욱 섬세한 튜닝이 필요하다.

lucene의 성능 확인을 위한 측정데이터 수집

측정데이터는 대부분 수치 정보인데 gnuplot는 공백이나 탭으로 구분된 필드단위로 데이터를 처리한다. 또한 주석을 위해서 #을 사용할 수도 있다. 다음은 gnuplot에 사용되는 데이터의 일반적인 포맷이다.
1  290
2  310
3  270
4  121
5  168
6  765
....

이제 lucene의 성능측정데이터를 수집해 보기로 하자. 성능 측정 데이터는 다음과 같은 방법으로 수집했다. 이미 웹문서에 대한 수집을 끝내고 색인파일까지 만들어 져 있다고 가정하겠다.
  1. lucene 색인 테이블로 부터 테스트에 사용할 단어 (Term)을 추출해서 파일로 저장한다. 대략 다음과 같은 내용을 가지게 될 것이다. 파일이름은 Term.dat로 했다.
    AJ
    AJAX
    AK
    AK47
    AKA
    AKARIN
    AKD
    AKIRA
    AKKO
    AKM
    AKUN
    AL
    ALBINO
    ALBUM
    ALCO
    ALDI
    ALEX
    ALFEN
    ALI
    ALIG
    ALIGN:
    ALL
    ALL=ko
    ALLEN
    ALO
    ....
  2. nutch의 테스트검색을 위해 제공된 NutchBean.java를 약간 수정해서 Term.dat로 부터 각 단어를 읽어서 검색어로 집어 넣고, 결과가 나올때까지의 시간과 검색결과의 갯수를 측정했다. 다음과 같은 결과가 나왔다.
    #검색결과갯수 검색시간(msec)  형태소분석된 검색어::오리지날 검색어
    16140 43 #boy::BOY
    3805 7 #bt::BT
    15282 4 #ba::Ba
    159 1 #backgr::Backgr
    0 1 #baiken::Baiken
    0 1 #baltazar::Baltazar
    0 0 #banlie::Banlie
    0 1 #barbares6::Barbares6
    0 0 #barto::Barto
    0 1 #basquiat::Basquiat
    0 0 #baye::Baye
    0 7 #beachim::BeachIM
    2350 42 #becom::Becom
    0 1 #beijing::Beijing
    0 1 #believeuserbar::BelieveUserBar
    0 2 #bera::Bera
    0 3 #bernadette::Bernadette
    1064 43 #beta2::Beta2
    16 5 #betra::Betra
    0 1 #bigban::BigBan
    0 1 #bilb::Bilb
    0 0 #biology::Biology
    0 1 #birthdayhasta::BirthdayHASTA
    4112 140 #bitmap canvas draw::Bitmap.Canvas.Draw
    15261 43 #bla::Bla
    16742 8 #ble::Ble
    0 1 #blocksignalson::BlockSignalsOn
    0 1 #bloodspell::BloodSpell
    0 0 #bluenote::BlueNote
    1570 71 #bm::Bm
    0 1 #bohemia::Bohemia
  3. gnuplot를 이용해서 제대로된 그래프를 그리기 위해서는 x혹은 y축 중 한쪽이 정렬되어 있어야 한다. 필자는 검색결과갯수를 x축으로하고 정렬하기로 하고, 간단한 정렬 프록그램 하나 만들었다.
    #include <map>
    #include <string>
    #include <stdio.h>
    #include <string>
    
    using namespace std;
    struct Tinfo
    {
      int time;
      char comment[1024];
    };
    int main(int argc, char **argv)
    {
      FILE *fp;
      char buf[1024];
      int a, b;
      Tinfo dinfo;
      multimap <int, Tinfo> sdata;
      multimap <int, Tinfo>::iterator mi;
      char *sp;
      fp = fopen(argv[1], "r");
      if (fp == NULL)
      {
        return 1;
      }
      while(fgets(buf, 1024, fp))
      {
        sscanf(buf, "%d %d", &a, &dinfo.time, dinfo.comment);
        sp = strstr(buf,"#");
        sprintf(dinfo.comment, "%s", sp);
        sdata.insert(pair<int, Tinfo>(a, dinfo));
      }
    
      mi = sdata.begin();
      while(mi != sdata.end())
      {
        printf("%d %d %s", mi->first, mi->second.time, mi->second.comment);
        *mi++;
      }
    }
  4. 프로그램을 실행시킨 결과 다음과 같이 정렬된 데이터를 가진 파일을 얻어냈다. 파일명은 TestOne.dat 로 저장했다.
    0 2 #achille::ACHILLE
    0 1 #aepfe::AEPFE
    0 1 #alsce::ALSCE
    0 1 #amsposx2::AMSPosX2
    0 1 #argazer::ARGAZER
    0 1 #actionsc::ActionSc
    0 2 #act퇴원::Act퇴원
    0 0 #adolescencegundam::AdolescenceGundam
    0 1 #afflu::Afflu
    0 1 #ageh::Ageh
    ...
    27 34 #vola::vola
    27 1 #미남급::미남급
    27 39 #바카니발::바카니발
    27 4 #비버::비버
    27 1 #역질::역질
    28 10 #alsw::alsw
    28 26 #ey2::ey2
    28 6 #litiga::litiga
    ...
    746253 657 #www fire::www.fire
    760181 387 #http 3a::http%3A%
    767602 232 #기분::기분
    768382 613 #www hatena ne::www.hatena.ne
    788605 1206 #www beach::www.beach
    799505 990 #a 2f 2f202::A%2F%2F202
    799592 451 #a ara::a:\ara
    799931 608 #a ac kr::a.ac.kr
    801479 2611 #an a::an.a
    829650 967 #www copyright o::www.copyright.o
    843166 2114 #from b::From.B
    848638 2312 #ed o a::ED.O.A
    881953 269 #필요::필요
    901553 1953 #e 9::e=9
    948204 1338 #esize 4::eSize*4
    969003 528 #x loliweb com::x.loliweb.com
    972308 4709 #t 0::t\0
    979552 1689 #deptno 3::deptno=3

측정데이터로 부터 간단한 이미지 얻어내기

gnuplot는 쉘(:12)과 비슷한 프롬프트 입력방식이다. 프롬프트에서 명령을 내려서 x축 데이터, y축 데이터를 설정하고, 읽어들일 데이터파일을 설정하는 식으로 해서 그래프를 그린다. 그러나 이렇게 하면 꽤나 작업하기가 귀찮으므로, 보통은 파일을 만들어서 읽어들이는 방식으로 그래프를 그린다. 위의 데이터를 이용해서 그래프를 그리기 위해서 다음과 같은 gnuplot 스크립트 파일을 만들었다. 파일이름은 test1.dem으로 했다.
set ylabel "millisec"
set xlabel "searchnum"
plot  "testone.dat" using 1:2 t "Search Test" with l 8
pause -1 "Hit return to continue"
  • set ylabel : y축에 사용될 라벨
  • set xlabel : x축에 사용될 라벨
  • plot .... : 실제 그래프를 그리는 영역이다. testone.dat파일을 열어서 1번째 필드의 값을 x축으로 2번째 필드의 값을 y축으로 한다. title tSearch Test라고 하고 선은 line l으로 색은 미리정의되어 있는 8번색으로 그려라고 명령한다. 색은 1부터 10까지 선택해서 사용할 수 있다.
  • pause -1 : 입력이 있을 때까지 그래프를 계속 보여준다.
스크립트파일을 작성했다면 다음과 같이 간단하게 실행할 수 있다.
# gnuplot test1.dem
다음은 결과 그래프다. attachment:test1.png

다음의 명령을 이용하면 그래프를 좀더 편하게 볼 수 있다.
  • g : 그래프 영역에 격자(grid)를 표시한다.
  • 마우스 드래그 : 마우스 왼버튼을 누르고 범위를 지정하면, 해당 영역을 자세히 볼 수 있다.
  • p : 이전 그래프 상태로 되돌아간다.

측정된 데이터로 부터 튜닝 포인트 찾아내기

검색된 문서의 갯수가 많을 수록 시간이 늘어나는 방향의 그래프를 보여주긴 하지만, 중간 중간 심하게 퇴는 부분이 눈에 띈다. 무언가 검색엔진에 문제가 있음을 암시해주고 있다. 그래프의 튀는 점과 testone.dat의 데이터를 확인해본결과 2개 이상의 쿼리가 주어졌을 때 시간이 급격하게 늘어나고 있음을 확인할 수 있었다.
829650 967 #www copyright o::www.copyright.o
843166 2114 #from b::From.B
848638 2312 #ed o a::ED.O.A
881953 269 #필요::필요
901553 1953 #e 9::e=9
948204 1338 #esize 4::eSize*4
www.copyright.o 와 같은 경우 하나의 검색어로 입력이 되었지만 형태소분석을 거치면서 3개의 단어로 분리가 되었다. 때문에 3번의 쿼리가 발생하고 시간지연이 생겼다.

그럼 단일쿼리가 주어질 경우 어떤 그래프를 보여줄지가 궁금해 졌다. 그래서 testone.dat에서 단일 쿼리 결과만 가져오기 위한 perl 프로그램을 하나 만들었다.
#!/usr/bin/perl
open ($handle, "$ARGV[0]") or die "File Not Found $ARGV[0]";

while($line = <$handle>)
{
  @list = split(" ",$line);
  $length = @list;
  if ($length == 3)
  {
    print $line;
  }
}
공백문자로 분리한다음 배열의 갯수가 3인것만 출력하도록 하는 식으로 단일 쿼리 결과만을 추출해 냈다. 파일의 이름은 testtwo.dat로 했다. 다음은 단일쿼리 결과에 대한 그래프 화면이다.

attachment:test2.png

2개의 데이터 비교하기

이런식으로 해서 많은 수의 측정자료가 만들어 졌다. 문제는 이들이 각각의 단일 파일로 존재한다는 것으로 정확한 측정을 위해서 각 데이터를 비교할 때 어려움이 생긴다. 일단 위의 두개의 측정 그래프를 동시에 보고자 할 경우 한눈에 들어오지 않을 것이다. 이 경우는 y축의 스케일이 다르기 때문으로 gnuplot의 명령을 통해서 해결할 수 있지만. 그렇다고 해도, 2개의 그래프를 보는건 아무래도 직관적이지 못하다.

gnuplot는 여러개의 데이터를 하나의 그래프에 쉽게 나타낼 수 있다. 다음은 testone.dat와 testtwo.dat를 하나의 그래프에 나타내게끔 재작성한 gnuplost 스크립트 파일이다.
set ylabel "millisec"
set xlabel "searchnum"
plot  "testtwo.dat" using 1:2 t "Search Test 1" with l 7, "Step100.dat" using 1:2 t "Search Test 2" with l 4

pause -1 "Hit return to continue"
다음은 결과 그래프다. 그냥 ,를 이용해서 쭈욱 나열하면 된다. 그래프의 구분을 쉽게 하기 위해서 title과 색을 달리 했다.

attachment:test3.png

한눈에 쏙들어온다. pharse 쿼리에 대해서 검색시간이 비정상적으로 증가하고 있으므로, 튜닝포인트를 pharse 쿼리 처리하는 쪽에 맞추어야 한다는 것을 알 수 있다. 실제로 제대로 성능이 나오게 하려면, 상당히 많은 부분을 수정해야 하며, 이러한 성능측정 작업을 반복해서 튜닝포인트의 범위를 좁혀나가야 한다. 여기에 대한 내용은 이문서의 범위를 벗어나므로 언급하지 않도록 하겠다.

이걸 Execl 로 그래프를 그렸다고 생각해보라. 시간이 걸리는건 둘째치고 아마 처리자체를 못할 것이다.

참고문서

  • lucene 쿼리분석에서 문서의 가중치를 구하는 score 알고리즘에 사용되는 정규화관련 식에 대한 그래프를 구하기 위해서 gnuplot를 사용했다. 이 그래프는 score알고리즘을 튜닝하기 위한 자료로 사용될 수 있다.
  • /dev/random 이용하기에서 random()함수의 성능을 확인하기 위해서 gnuplot를 사용하고 있다.
  • GNU Plot 메뉴얼