ECTL_GETSTRING работает слишком медленно

Stanislav V. Mekhanoshin [rampitec@tu.spb.ru]rampitec@tu.spb.ru

Предположим, что плагину нужно пройтись последовательно по большому количеству строк редактора. В моем случае, плагин Incremental Search осуществлял поиск подстроки в редакторе. Первая мысль, которая приходит в голову - сделать последовательный перебор строк примерно так:

{
    struct EditorGetString egs;
    struct EditorSetPosition esp;
    struct EditorInfo ei;

    Info.EditorControl(ECTL_GETINFO,&ei);

    for( egs.StringNumber=ei.CurLine;
         egs.StringNumber < ei.TotalLines;
         egs.StringNumber++ )
    {
        Info.EditorControl(ECTL_GETSTRING,&egs);
        if( process( egs.StringText, egs.StringLength ) ){
            esp.CurLine=egs.StringNumber;
            esp.CurPos=-1;
            esp.CurTabPos=-1;
            esp.TopScreenLine=-1;
            esp.LeftPos=-1;
            esp.OverType=-1;
            Info.EditorControl(ECTL_SETPOSITION,&esp);
            return TRUE; // Успех, установили текущую строку.
        }
    }
    return FALSE; // Неуспех, просто вернемся. В редакторе ничего не
                  // изменилось.
}

Однако, написав таким образом, я обнаружил, что код обработки строки (собственно, функция process()), работает на два порядка быстрее, чем весь этот цикл в целом. Т.е. процедура взятия строки по номеру поглощает ~99% времени.

Код был переделан по совету, предложенному ER (не только мне, также поступил в плагине EditCompletition, Андрей Третьяков). Суть совета заключается в том, чтобы брать строку не по номеру, а всегда текущую (-1). Т.е. заменить ECTL_GETSTRING с номером строки на пару ECTL_SETPOSITION с номером строки и ECTL_GETSTRING с -1.

Следует заметить, что этот метод требует запоминания текущей позиции в редакторе и ее восстановления при откате. Но дело того стоит. Итак, код приведенный выше следует переписать следующим образом:

{
    struct EditorGetString egs;
    struct EditorSetPosition esp;
    struct EditorInfo ei;

    Info.EditorControl(ECTL_GETINFO,&ei);
    egs.StringNumber=-1;

    for( esp.CurLine=ei.CurLine;
         esp.CurLine<ei.TotalLines;
         esp.CurLine++ )
    {
        Info.EditorControl(ECTL_SETPOSITION,&esp);
        Info.EditorControl(ECTL_GETSTRING,&egs);
        if( process( egs.StringText, egs.StringLength ) )
            return TRUE; // Успех. Строка уже установлена.
    }

    // восстановим старую позицию:

    esp.CurLine=ei.CurLine;
    esp.CurPos=ei.CurPos;
    esp.TopScreenLine=ei.TopScreenLine;
    esp.LeftPos=ei.LeftPos;
    esp.CurTabPos=-1;
    esp.OverType=-1;
    Info.EditorControl(ECTL_SETPOSITION,&esp);

    return FALSE;
}

Следует отметить, что FAR не отрисовывает изменения немедленно, так что "мельтешения" на экране не будет.

Теперь о приятном: замеры времени на моей машине показали ускорение взятия строки (только взятие, без обработки - чистое время) во втором случае по сравнению с первым в 63 раза. Причем эффект стабилен как для относительно небольших файлов, так и для файлов в половину и более моего RAM. Данные Андрея Третьякова примерно те же - у него соотношение 1/65. Т.е. цифры весьма близкие.

Для замера был использован run-time профилировщик Watcom C 11.0 на основе профилировочной инструкции Pentium rdtsc. IMHO самый правильный профайлер. Но даже на глаз ускорение очень заметно. Моя конфигурация: IP-240, 96M RAM, Windows NT 4.0 SP6. Андрей Третьяков проводил испытания на 486-dx4-100.

Небольшое предостережение: при установке позиции FAR может изменить значения LeftPos, TopScreenLine, CurPos даже если вы использовали для них -1. Например, если курсору запрещено перемещаться за конец строки, а строка короче, чем CurPos, который вы пытаетесь сохранить, устанавливая в -1, то CurPos, тем не менее, изменится. В большинстве случаев такое поведение вполне удовлетворяет пользователя. Однако, при последовательном переборе строк в приведенном цикле, пользователь не видит промежуточных перемещений по тексту и будет удивлен, увидев не совсем ту позицию на экране, на которую он мог бы рассчитывать при перемещении (с его точки зрения), например, с 1-ой на 10-ю строку. В первом приведенном примере такой проблемы возникнуть не может, т.к. реально также осуществляется только одно перемещение. Второй же пример следует модифицировать таким образом, чтобы избежать подобной проблемы.

Вариантов модификации может быть много. Например, если ваш плагин вообще никогда не меняет текущую позицию (т.е. всегда ее восстанавливает), то этой проблемы не будет. Если плагин имеет код вычисления TopScreenLine, LeftPos, CurPos исходя из каких-то своих соображений, возможно вообще не относящихся к предыдущему их состоянию, то он просто вызовет этот код после завершения цикла. В моем случае, я всегда восстанавливаю сохраненную позицию, а уже затем использую ECTL_SETPOSITION, передавая в нее нужную мне строку и -1 для остальных параметров. Вот пример модифицированного кода:

{
    struct EditorGetString egs;
    struct EditorSetPosition esp;
    struct EditorInfo ei;
    int    nFound=-1; // номер найденной строки

    Info.EditorControl(ECTL_GETINFO,&ei);
    egs.StringNumber=-1;

    for( esp.CurLine=ei.CurLine;
         esp.CurLine < ei.TotalLines;
         esp.CurLine++ )
    {
        Info.EditorControl(ECTL_SETPOSITION,&esp);
        Info.EditorControl(ECTL_GETSTRING,&egs);
        if( process( egs.StringText, egs.StringLength ) ){
            nFound=esp.CurLine; // Успех
            break;
        }
    }

    // восстановим старую позицию:

    esp.CurLine=ei.CurLine;
    esp.CurPos=ei.CurPos;
    esp.TopScreenLine=ei.TopScreenLine;
    esp.LeftPos=ei.LeftPos;
    esp.CurTabPos=-1;
    esp.OverType=-1;
    Info.EditorControl(ECTL_SETPOSITION,&esp);

    if( nFound >= 0 )
    {               // А теперь снова установим на найденное место...
        esp.CurLine=nFound;
        esp.CurPos=-1;          // Невзирая на то, что эти поля уже
        esp.TopScreenLine=-1;   // содержат значения - их надо выставить
        esp.LeftPos=-1;         // в -1. Это не одно и то же! Явное число -
        esp.CurTabPos=-1;       // безусловно. -1 лишь _пытается_ сохранить
        esp.OverType=-1;        // старое значение, если это возможно!

        Info.EditorControl(ECTL_SETPOSITION,&esp);
    }

    return nFound >= 0;
}

 
28.11.1999
Rev. 26.06.2000
наверх