. 전방 정렬 생략

ApiEdit는 문서에 조금이라도 변화가 있으면 버퍼의 처음부터 끝까지 재정렬한다. 물론 정렬방식은 이전 줄의 정렬 결과를 참고하는 기법으로 어느 정도 최적화가 되어 있기는 하지만 첫 줄부터 끝까지 다시 정렬하는 것은 아주 극단적으로 무식한 방법이며 ApiEdit를 느리게 만드는 일등 공신이다. UpdateLineInfo 함수는 아주 중요한 일을 하지만 최적화가 되지 않은 상태에서는 아주 역적 같은 존재인 것이다.

실제로 문서를 편집할 때 정렬이 변경되는 부분은 지극히 일부밖에 없다. 예를 들어 총 만 줄짜리 문서에서 9900줄을 편집중에 있다고 하자. 그러면 현재 줄이 속한 문단의 정렬 상태는 변하겠지만 그 이전 문단은 정렬 상태가 변할 리 만무하다. , 편집중인 문단의 전방으로는 정렬을 생략해도 된다는 얘기며 만 줄 정렬할 것을 백 줄만 정렬하면 되므로 얼마나 속도가 빨라지겠는가?

UpdateLineInfo 함수를 최적화하는 방식은 바로 이런 식이다. 꼭 필요하다고 판단되는 부분만 재정렬하는 것이다. 그런데 문제가 있다. 이 함수는 어떠한 인수도 받아들이지 않으며 문서의 어디쯤이 얼마만큼 변경되었는지 전혀 알지 못하기 때문에 정렬해야 할 위치를 제대로 판단할 수가 없다는 점이다. , 이 함수가 좀 더 똑똑해지기 위한 추가적인 정보가 없는 것이다. 그래서 외부에서 이 함수에게 정렬을 위한 힌트를 제공해야 하며 이 함수는 힌트를 받아 좀 더 지능적으로 정렬을 하도록 동작을 수정해야 한다. 함수의 원형은 다음과 같이 수정된다.

 

void UpdateLineInfo(int nPos=-1, int nCount=-1);

 

두 개의 힌트 정보를 인수로 받아 들이는데 nPos는 편집이 일어난 오프셋 위치이며 이 위치 이후부터 정렬하면 된다. nCount는 편집된 바이트 수인데 삽입된 경우는 양수값을 가지며 삭제된 경우는 음수값을 가진다. 두 인수의 디폴트값은 -1로 되어 있으며 -1이면 힌트가 없으므로 전체 정렬을 하라는 뜻으로 해석한다. 힌트를 제공하는 주체는 Insert, Delete 함수이며 이 함수에서 UpdateLineInfo를 호출할 때 가지고 있는 정보를 같이 넘겨 주도록 수정한다.

 

void Insert(int nPos, TCHAR *str)

{

     ....

     UpdateLineInfo(nPos,lstrlen(str));

 

void Delete(int nPos, int nCount)

{

     ....

     UpdateLineInfo(nPos,-nCount);

 

이렇게 힌트를 제공하면 UpdateLineInfo 함수는 편집된 부분만 정렬하도록 최적화를 할 수 있다. Insert, Delete 외에 정렬함수를 호출하는 곳으로는 OnSize, SetWrap 함수가 있는데 이 두 함수에서는 정렬 상태가 왕창 바뀌므로 일부 정렬이 불가능하다. 그래서 디폴트 인수인 -1,-1을 그대로 전달하도록 내버려 두면 전체 정렬을 하게 된다.

정렬 최적화를 위한 힌트 정보를 전달받았으므로 이 정보를 최대한 활용하여 정렬루틴을 최적화해보자. 먼저 nPos 값으로부터 정렬을 시작할 줄을 찾고 그 이후 줄만 정렬하도록 해보자. , 편집이 일어난 위치의 전방으로는 정렬을 생략하고 후방으로만 정렬을 다시 하는 것이다. 완성된 코드는 다음과 같다.

 

void UpdateLineInfo(int nPos/*=-1*/, int nCount/*=-1*/)

{

     int l,s,e;

     int nPara, nLine=0;

     int i;

    int ParaStart;

 

    if (nPos==-1) {

        l=0;

        nPara=0;

    } else {

        ParaStart=FindParaStart(nPos);

        if (ParaStart == 0) {

           l=0;

           nPara=0;

        } else {

           GetRCFromOff(ParaStart-2,l,e);

           nPara=pLine[l].nPara+1;

           l++;

        }

    }

 

    for (;;l++) {

          if (l >= Linelen) {

          ....

 

이전 코드에서는 0번째 줄, 0번째 문단부터 정렬을 시작했었으나 이제 무조건 처음부터 정렬하지 않는다. nPos가 전달되었으면 이 오프셋이 속한 문단의 첫 줄부터 정렬하면 된다. 정렬 결과 같은 문단에 속한 앞 줄이 영향을 받을 수도 있으므로 nPos 위치의 줄부터 정렬을 하는 것이 아니라 그 줄이 속한 문단의 첫 줄부터 정렬해야 한다.

이 코드에서 가장 큰 문제는 편집이 일어난 위치 nPos로부터 이 문단의 첫 줄번호를 구하는 것이다. 문단의 시작 오프셋은 FindParaStart 함수로 쉽게 구할 수 있으므로 문단의 시작 오프셋 ParaStart로부터 이 줄의 번호를 구하는 것이 문제다. 일단 다음 코드를 생각할 수 있다.

 

ParaStart=FindParaStart(nPos);

GetRCFromOff(ParaStart,l,e);

 

ParaStart오프셋으로 GetRCFromOff 함수를 호출하면 줄번호를 쉽게 구할 수 있다. 열번호는 필요없으므로 더미 인수 e를 그냥 던져 주기만 했다. 지금까지의 알고리즘대로라면 이 방법은 정상적이며 지극히 잘 동작할 것이다. 그러나 여기에 아주 심각한 문제가 있으며 이 문제는 최적화의 큰 걸림돌이 된다.

UpdateLineInfo 함수는 pLine 배열에 정렬 결과를 만드는데 불필요한 정렬을 하지 않기 위해 nPos로부터 정렬을 시작할 줄을 찾으며 이때 GetRCFromOff 함수를 호출했다. 그런데 GetRCFromOff 함수는 줄번호를 찾기 위해 pLine 배열을 참조한다. , pLine을 만들고 있는 중에 pLine을 참조하려고 하는 아주 특수한 상황이 된 것이다. GetRCFromOff 함수가 참조하는 pLine은 무효이며 결론적으로 위 코드는 제대로 동작하지 않을 뿐더러 엉뚱한 메모리를 건드릴 수 있는 아주 위험한 코드가 되어 버렸다.

그래서 UpdateLineInfo 함수는 문단 처음의 줄번호를 직접 구하는 대신 그 이전 문단의 마지막 줄번호를 구하고 이 줄번호에 1을 더해 목표로 하는 정렬 시작줄을 찾았다. 문단 처음 오프셋에서 2를 빼면 이전 문단의 개행코드에 걸린다. 이 문단은 편집되지 않은 상태로 있으므로 GetRCFromOff를 안전하게 호출할 수 있으며 이때 구해진 줄번호에 1을 더하면 원하는 줄번호를 얻게 된다.

이후의 정렬루틴은 전방 정렬은 생략하고 이 코드에서 찾은 시작줄 l부터 정렬을 하게 되므로 정렬 속도가 빨라진다. 특히 문서의 뒷부분에 있을수록 속도개선 효과가 극대화되는데 이제 5MB의 큰 파일을 열어 놓은 상태에서도 문서 뒷부분에서는 별다른 속도 저하없이 문서를 편집할 수 있을 것이다. 그러나 아직 문서 처음에서는 여전히 느리다.

이런 식으로 은근슬쩍 위험한 코드를 비켜 나갔는데 보다시피 대충 보기에는 맞아 보이지만 굉장히 불안한 모습을 하고 있다. 코드를 보면 왠지 허약해보고 뭔가 논리상의 허점이 있을 것 같다는 예감이 들게 마련이다. 이런 코드를 작성할 때는 이 코드가 어떤 경우라도 문제가 없다는 증명을 하고 넘어 가야 하며 문제가 있다면 그 문제에 대해 정확하게 대처하도록 만들어야 한다. 이 코드에서 그런 흔적을 몇 군데 발견할 수 있다.

 

nPos -1이면 전체 정렬이므로 정렬 시작줄은 0이고 문단 시작도 0이다. -1은 오프셋이 아니라 일종의 신호이므로 예외 처리해야 한다.

ParaStart 0이면 즉, 이 문단이 문서의 첫 문단이면 이전 문단이 없다. 이때 ParaStart-2의 줄번호를 조사하려고 했다가는 큰일날 것이다. 첫 문단이라면 더 계산할 필요없이 처음부터 정렬하면 된다.

ParaStart-2가 과연 이전 문단의 마지막 개행코드가 맞는가를 확실히 하자. FindParaStart가 문단 첫 오프셋을 찾아 주었고 이 문단이 첫 문단이 아니므로 ParaStart-2는 반드시 존재하며 이전 문단의 마지막이 분명하다. 만약 이 부분에 문제가 있다면 FindParaStart가 잘못된 것이므로 이 함수를 수정해야 한다.

이전 문단의 정렬정보는 확실히 믿을 수 있는가? 편집되지 않은 문단이므로 확실히 믿을 수 있다.

 

설사 이 논리에 대해 문제가 없음을 확신한다 하더라도 최적화 전보다는 코드가 훨씬 더 복잡해졌고 이후의 확장이나 유지에 어려움이 있게 된다. 이것이 바로 최적화에 의한 불이익이며 가급적이면 최적화를 늦게 해야 하는 이유이다.