ECTL_GETSTRING works very slowly
Stanislav V. Mekhanoshin ?subject=Articles">[email protected]
Let's suppose a plugin is to scan a large number of strings in the editor in sequence. In my case, the Incremental Search plugin searched for a substring in the editor. The first idea was to perform a sequential search for strings in this way:
{ 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,⪖); 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; // Success, the string is set now. } } return FALSE; // Fail, just return back. There are // no changes in the editor. }
However, having code written in this way, I discovered that string processing code (essentially the process() function) worked considerably faster than the whole iteration. In other words, the procedure that returned the string by its number took ~99% of time.
The code was rewritten according to the ER's advice (Andrew Tretyakov did the same in the EditCompletion plugin). Essentially, the advice is to obtain a current string (-1) always, without using its real number. In other words, to substitute ECTL_GETSTRING with the string number for twain ECTL_SETPOSITION with the string number and ECTL_GETSTRING with -1.
Need to mention that you must store the current cursor position in the editor and restore it when doing rollback in order to use this method. But the matter is worthy of it. So, you must rewrite the code mentioned above in this way:
{ 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,⪖); if( process( egs.StringText, egs.StringLength ) ) return TRUE; // Success, the string is set now. } // Restore the old position: 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; }
By the way, FAR doesn't redraw changes immediately, so the screen won't flicker.
And the most pleasant: time metering performed on my computer showed that we get the string (only get, without processing - the raw time) 63 times faster in the second case than in the first case. The effect is stable for both relatively small files and files with size more than half of my RAM. Andrew Tretyakov has almost the same results - he has ratio of 1/65. In other words, the figures are rather close.
rdtsc
Pentium profiling instruction. IMHO it's the best profiler.
But speed-up is highly noticeable even without any tools. All
tests were performed using IP-240, 96Mb RAM, Windows NT 4.0 SP6.
Andrew Tretyakov used 486-dx4-100 for metering.
Minor warning: When setting the position, FAR may change the LeftPos, TopScreenLine, and CurPos values even if you set them to -1 already. For example, if the cursor can't move beyond the end of the line, but the line is shorter than CurPos you try to store by setting it to -1, then CurPos will change despite of that. Such behaviour is acceptable for most users. However, user doesn't see intermediate moves through the text when searching strings sequentially within the iteration mentioned above. He will be surprised seeing the position he doesn't expect (from his point of view) when moving from the 1st to the 10th string. Such problem can't appear in the first example since only one move is actually performed. But you should modify the second example in order not to face with problem like that.
There are many ways to modify it. For example, this problem gets eliminated if your plugin doesn't change the current position at all (i.e. always restores it). If the plugin computes TopScreenLine, LeftPos, or CurPos values according to the its own concept (perhaps not related to their previous state), it just calls this code after the iteration is finished. In my case I always restore the stored position, and then use the ECTL_SETPOSITION by passing the required string and -1 for other parameters there. Here's the example of the modified code:
{ struct EditorGetString egs; struct EditorSetPosition esp; struct EditorInfo ei; int nFound=-1; // number of the found string 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,⪖); if( process( egs.StringText, egs.StringLength ) ){ nFound=esp.CurLine; // Success break; } } // Restore the old position: 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 ) { // Now set again to the found position... esp.CurLine=nFound; esp.CurPos=-1; // Despite these fields contain values esp.TopScreenLine=-1; // already, they must be set to -1. esp.LeftPos=-1; // It's not the same! Explicit number is esp.CurTabPos=-1; // unconditional. -1 only _tries_ to store esp.OverType=-1; // the old value, if possible! Info.EditorControl(ECTL_SETPOSITION,&esp;); } return nFound >= 0; }
28.11.1999
Rev. 26.06.2000