. 최적화 준비

사용 가능한 메모리의 양이 이론적으로 무한대가 되었으며 실질적으로도 수십 MB 정도의 텍스트는 거뜬히 불러 올 수 있게 되었다. 이렇게 되면 작은 파일을 편집할 때는 잘 느끼지 못했던 속도의 저하를 다시 경험할 수 있게 되는데 이 즈음에서 중간 최적화가 필요하다. 이번 최적화는 주로 큰 파일을 편집할 때의 시간을 단축시키는 데 중점을 두는데 훨씬 더 정교하고 복잡하지만 당장 눈에 띌 만한 큰 효과는 기대하기 어렵다.

1차 최적화 때는 수천 배의 속도 향상 효과가 있었지만 중간 최적화는 기껏해야 2배 내지는 10배 정도의 속도 향상 효과가 있을 것이다. 그래도 2배만 빨라져도 얼마나 다행인가? 마지막 최적화 때는 불과 몇 % 향상을 위해 코드를 고쳐야 하는데 중간 최적화는 그래도 그에 비한다면 효과가 많은 셈이다. 근본적으로 구조를 뜯어 고치지는 않으며 불필요한 동작을 생략함으로써 시간을 단축시키는 방법을 많이 사용하게 될 것이다.

중간 최적화를 하기 전에 몇 가지 작업 준비를 하도록 하자. 큰 파일을 편집할 때 어떤 점이 문제가 되고 어디가 문제가 되는지를 조사해야 최적화 대상을 제대로 물색할 수 있으므로 성능 점검을 위한 간단한 준비를 해두고 작업을 시작한다. 먼저 다음 속도 측정 매크로를 소스의 선두에 붙여넣는다.

 

#define STARTQ TCHAR qMes[256];\

     static qcount=0;\

     LARGE_INTEGER qs,s1,s2,s3,qe,qfreq;\

     double g1,g2,g3,ge;\

     QueryPerformanceFrequency(&qfreq);\

     QueryPerformanceCounter(&qs);\

     s1=s2=s3=qs;

#define SPOT1 QueryPerformanceCounter(&s1);

#define SPOT2 QueryPerformanceCounter(&s2);

#define SPOT3 QueryPerformanceCounter(&s3);

#define ENDQ QueryPerformanceCounter(&qe);\

     g1=((double)(s1.QuadPart-qs.QuadPart))/((double)qfreq.QuadPart);\

     g2=((double)(s2.QuadPart-qs.QuadPart))/((double)qfreq.QuadPart);\

     g3=((double)(s3.QuadPart-qs.QuadPart))/((double)qfreq.QuadPart);\

     ge=((double)(qe.QuadPart-qs.QuadPart))/((double)qfreq.QuadPart);\

     sprintf(qMes, "%04d : S1=%.6f,S2=%.6f,S3=%.6f,QE=%.6f\r\n",++qcount,g1,g2,g3,ge);\

     OutputDebugString(qMes);

 

내용을 보면 알겠지만 이 매크로는 특정 지점의 실행 시간을 측정하여 디버깅 정보로 출력하는 역할을 한다. 디버깅 정보는 컴파일러의 출력창(Output)에 문자열로 나타난다. 시간을 측정하고 싶은 곳의 선두에 STARTQ 매크로를 삽입하고 작업 완료 시점에 ENDQ 매크로를 삽입하면 두 지점간의 경과 시간이 디버깅 정보로 출력된다. 중간에 시간 점검 지점을 세 군데 더 둘 수 있으므로 최대 4군데의 시간을 측정할 수 있다. 사용 예를 보이면 다음과 같다.

 

STARTQ

함수1호출

SPOT1

함수2호출

SPOT2

연산식

ENDQ

 

이 매크로에 의해 함수1 호출 후의 경과 시간과 함수 2 호출 후의 경과 시간, 연산식 실행 후의 경과시간을 조사할 수 있다. 전체적으로 0.033초가 걸렸고 SPOT1의 경과 시간이 0.0002초 걸렸고 SPOT2의 경과 시간이 0.032초가 걸렸다면 함수2가 시간을 많이 잡아 먹고 있다는 것을 알 수 있게 된다. , 이 함수가 속도 저하의 주범이며 최적화 대상이 된다.

이 매크로는 재활용이 쉽도록 작성하였으므로 소스 선두에 대충 복사해 넣고 시간 측정용으로 애용할만하다. 함수2가 느리다는 것을 확인했으면 다시 함수2에 매크로들을 중간중간 삽입해 넣고 함수2의 어떤 부분이 느린지를 정밀하게 테스트할 수 있다. 이런 식으로 범위를 좁혀 나가다 보면 최적화 대상을 쉽게 찾을 수 있다.

다음은 아주 큰 텍스트 파일을 준비하도록 하자. 직접 텍스트 파일을 만들어도 되고 아니면 CD-ROM에 있는 oop-long.txt 파일을 대신 사용해도 된다. 이 파일은 같은 내용을 여러 번 복사함으로써 무려 5MB의 크기를 가지고 있으며 총 줄 수는 96000줄에 달한다. 이 정도 길이의 문서를 별 속도 저하없이 편집할 수 있다면 텍스트 편집기로서 기본 기능은 갖추었다고 할 수 있다.

ApiEdit는 아직 파일을 입출력하는 코드를 가지고 있지 않으므로 임시적으로 이 파일을 읽어올 수 있는 기능을 작성하도록 하자. 사용하지 않는 키인 VK_F8에 이 파일을 불러와 버퍼에 넣어주는 임시 코드를 작성한다.

 

void OnKey(HWND hWnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)

{

     ....

     case VK_F8:

          HANDLE hFile;

          DWORD dwRead, dwSize;

          TCHAR *TextBuf;

          hFile=CreateFile("oop-long.txt",GENERIC_READ,0,NULL,

              OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

          if (hFile!=INVALID_HANDLE_VALUE) {

              dwSize=GetFileSize(hFile,NULL);

              TextBuf=(TCHAR *)malloc(dwSize+1);

              ReadFile(hFile,TextBuf,dwSize,&dwRead,NULL);

              TextBuf[dwRead]=0;

              CloseHandle(hFile);

              off=0;

              PrevX=0;

              bLineEnd=FALSE;

              xMax=1024;

              xPos=0;

              yPos=0;

              SelStart=SelEnd=0;

              ClearBookmark();

 

              lstrcpy(buf,"");

              Insert(0,TextBuf);

              free(TextBuf);

 

              SetCaret();

              Invalidate(-1);

          }

          return;

 

파일을 읽은 후 Insert 함수로 버퍼에 삽입했는데 삽입 시점에서 정렬과 스크롤 정보 갱신을 할 것이다. oop-long.txt는 프로젝트 폴더에 있는 것으로 가정하고 있으므로 복사를 해놓던가 아니면 적절한 절대경로에 복사해놓고 코드의 경로를 고쳐 주도록 하자. 실행중에 F8을 누르면 oop-long.txt 파일을 읽어올 것이다. 컴파일한 후 실행해보고 이 정도 크기면 어느 정도의 속도가 나는지 테스트해보도록 하자.

F8을 눌렀을 때 파일을 읽어오는 속도는 아주 빠르다. 요즘 하드디스크 성능이 너무 좋아서 5MB 정도면 0.5초만에 다 읽을 수 있는 정도다. 그러나 읽어온 직후에 정렬을 처음부터 끝까지 해야 하므로 화면에 파일 내용이 출력되는 데는 약간의 시간이 필요하다. 96000줄을 죄다 스캔하면서 줄의 처음과 끝을 찾아내야 하므로 느린 게 당연하다. 자동개행을 하지 않도록 F9를 눌러 옵션을 조정하면 정렬 속도는 굉장히 빨라진다.

다음은 아래위로 이동해보고 스크롤도 해보고 마우스로 긁어서 선택도 해보자. 큰 파일인데도 느리지 않고 굉장히 빠르게 동작한다. 하지만 문서 처음에는 속도가 제대로 나지만 뒤쪽으로 갈 수록 느려져 문서 끝 부분에서는 사람의 동작을 제대로 따라오지 못한다. 왜냐하면 줄번호를 찾는 함수가 순차 검색을 하기 때문에 뒷부분으로 갈 수록 비교해보는 횟수가 증가하기 때문이다.

이번에는 문자를 입력해보자. 글자 하나를 입력하는데 거의 3초 가량 걸리며 한글의 경우 한 글자 입력하는데 10초 정도가 걸린다. 사람손을 못 따라올 뿐만 아니라 기다리기도 갑갑할 정도로 느려 거의 못쓸 지경이다. 입력 속도에 있어서는 뭔가 특단의 대책을 마련해야 할 필요가 있을 것 같다. 최악의 경우는 윈도우의 크기를 변경할 때인데 이때는 정렬을 처음부터 끝까지 다시 하기 때문에 아주 심하게 느려진다.

편집파일이 커지면 느려진다는 것을 눈으로 직접 확인해 봤으니 어떤 부분이 왜 느린지 조사해보고 속도를 개선해보도록 하자. 이 최적화가 끝나면 5MB 정도의 파일도 무리없이 다룰 수 있는 편집기가 되어 있을 것이다.