Bug 65806 - [misc] TextViewer.revealRange very slow for long ranges
Summary: [misc] TextViewer.revealRange very slow for long ranges
Status: RESOLVED FIXED
Alias: None
Product: Platform
Classification: Eclipse Project
Component: SWT (show other bugs)
Version: 3.0   Edit
Hardware: PC Windows XP
: P3 major (vote)
Target Milestone: ---   Edit
Assignee: Felipe Heidrich CLA
QA Contact:
URL:
Whiteboard:
Keywords: performance
Depends on:
Blocks:
 
Reported: 2004-06-04 16:17 EDT by Sam Mesh CLA
Modified: 2005-05-02 10:57 EDT (History)
4 users (show)

See Also:


Attachments
First compared file (14.12 KB, text/plain)
2004-06-04 16:19 EDT, Sam Mesh CLA
no flags Details
Second compared file (115.36 KB, text/plain)
2004-06-04 16:19 EDT, Sam Mesh CLA
no flags Details
screeshot (13.06 KB, image/jpeg)
2005-04-26 14:43 EDT, Felipe Heidrich CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Sam Mesh CLA 2004-06-04 16:17:51 EDT
 
Comment 1 Sam Mesh CLA 2004-06-04 16:19:25 EDT
Created attachment 11611 [details]
First compared file
Comment 2 Sam Mesh CLA 2004-06-04 16:19:53 EDT
Created attachment 11612 [details]
Second compared file
Comment 3 Sam Mesh CLA 2004-06-04 16:22:45 EDT
Hangs scenario:
- Select "first compared file" and "second compared file"
- Do "Compare with/Each other"
- Click "Select Next Change" several times...
- CPU - 100% - Eclipse hangs...
Comment 4 Andre Weinand CLA 2004-06-04 19:14:30 EDT
Are you sure Compare hangs?
The second file contains one line with > 100k characters.
Trying to step into this line took 5 minutes on my Mac, but otherwise worked fine.

I could not reproduce the hang, however, I see the issue with very long lines here.
Comment 5 Andre Weinand CLA 2004-06-05 06:44:59 EDT
I'm in the office now where I have a Win2k box:

There it really looks like the Compare is hung.
But it is not :-) It just takes much longer because
the default heap size on Window (64M?) is smaller than on Mac (150MB).

Debugging revealed the following:
Compare calls TextViewer.revealRange(67, 103293)
to reveal the third line in the document (which has the length 103293)

This calls TextViewer.getExtent(int start, int end)
which itself uses fTextWidget.getLocationAtOffset(i) for every character of
the 100000 char line.

I stepped further into the loop and it seems that 
fTextWidget.getLocationAtOffset is quite expensive and takes a long time.

I'm not sure that I can do something from Compare here.

Moving to platform.text for comment.
Comment 6 Sam Mesh CLA 2004-06-06 10:19:37 EDT
Andre, thanks for the tracing of the 'hang' causes - it actually doesn't hang -
but takes about 3 minutes on P4 3Ghz. :)

It'd be great to eliminate mentioned expensive part of the code.

PS. So, if I select in the text editor the line about 100K long - it'll 'hang'? :)
Comment 7 Kai-Uwe Maetzel CLA 2004-06-07 03:47:14 EDT
no action for 3.0
Comment 8 Douglas Pollock CLA 2004-06-14 15:55:27 EDT
Duplicate of Bug 50335? 
Comment 9 Sam Mesh CLA 2004-06-14 16:51:30 EDT
Do not think so.
Here, the problem is in platform.txt - TextViewer.revealRange().
There, the  problem is comparison itself.
Comment 10 Dani Megert CLA 2004-06-15 03:13:14 EDT
adapting summary
Comment 11 Tod Creasey CLA 2005-03-07 11:57:25 EST
Adding my name to the cc list as we are now tracking performance issues more
closely. Please remove the performance keyword if this is not a performance bug.
Comment 12 Mike Wilson CLA 2005-04-25 10:43:45 EDT
Re comment #5: "This calls TextViewer.getExtent(int start, int end) which itself uses 
fTextWidget.getLocationAtOffset(i) for every character of the 100000 char line."

That seems singularly inefficient. Why is that required?
Comment 13 Dani Megert CLA 2005-04-25 11:35:07 EDT
AFAIK this has to be done in order to get the correct extent for BIDI. I guess
the best way to improve this would be to have better support in StyledText.

Moving to SWT for comment. 
Comment 14 Mike Wilson CLA 2005-04-25 12:04:05 EDT
It's not at all clear to me why that should be true. At the very least, shouldn't we only need to compute 
the values on the segments where there are direction changes?
Comment 15 Dani Megert CLA 2005-04-25 12:09:02 EDT
Yep, but the methods to get the bidi segments aren't public.
Comment 16 Felipe Heidrich CLA 2005-04-25 14:09:51 EDT
Daniel, does TextViewer.getExtent(int start, int end) return a Rectangle[], a 
Region, or a Rectangle (including all Rectangles)?
If it returns a Rectangle we could add API to StyledText to expose 
TextLayout#getBounds(int start, int end).
The thing is, at this point of the release cycle adding new API is kind of 
hard.

---*---

StyledText caches the TextLayout for each visible line, so the first call to 
fTextWidget.getLocationAtOffset(i) should be slow and the rest much faster. 
Things to check: Is the cache being used, is the line visible, is TextViewer 
doing something between iteration that would cause the cache to be flushed 
(this would cause very poor performance).
Even with if cache on, for each iteration StyledText is getting the TextLayout 
(from the cache hopefully) again, and is calling all LineStyledListener again 
to make sure the line hasn't changed  (this can be pretty slow). 
Comment 17 Felipe Heidrich CLA 2005-04-25 14:27:18 EDT
Daniel, try adding this method to StyledText and using it from TextViewer.
Let me know how much it helps:

I hacked this is 2 min, I hope it works, it only work if start and end are 
within the same line.

public Rectangle getBounds (int start, int end) {
	checkWidget();
	int charCount = getCharCount();
	if (start < 0 || start > charCount) {
		SWT.error(SWT.ERROR_INVALID_RANGE);		
	}
	if (end < 0 || end > charCount) {
		SWT.error(SWT.ERROR_INVALID_RANGE);		
	}	
	int lineStart = content.getLineAtOffset(start);
	int lineEnd = content.getLineAtOffset(end);
	if (lineStart != lineEnd) SWT.error(SWT.ERROR_NOT_IMPLEMENTED);
	int lineOffset = content.getOffsetAtLine(lineStart);
	String line = content.getLine(lineStart);
	TextLayout layout = renderer.getTextLayout(line, lineOffset);
	Rectangle result = layout.getBounds(start - lineOffset, end - 
lineOffset);
	renderer.disposeTextLayout(layout);
	return result;
}
Comment 18 Dani Megert CLA 2005-04-26 07:02:00 EDT
>Daniel, does TextViewer.getExtent(int start, int end) return a Rectangle[], a 
>Region, or a Rectangle (including all Rectangles)?
returns IRegion:
/**
 * Returns the region covered by the given start and end offset.
 * The result is relative to the upper left corner of the widget
 * client area.
 *
 * @param start offset relative to the start of this viewer's view port
 * 	0 <= offset <= getCharCount()
 * @param end offset relative to the start of this viewer's view port
 * 	0 <= offset <= getCharCount()
 * @return the region covered by start and end offset
 */
final protected IRegion getExtent(int start, int end) {


>The thing is, at this point of the release cycle adding new API is kind of 
>hard.
Adding a new API (not changing existing one) to fix a performance problem will
not be that hard, we simply need to convince one of the PMC members ;-)

>Daniel, try adding this method to StyledText and using it from TextViewer.
>Let me know how much it helps:
I'll start now. Have to replace binary SWT with source which takes quite some time.

>I hacked this is 2 min, I hope it works, it only work if start and end are 
>within the same line.
That's how we currently use the method but unfortunately this isn't speced in
the Javadoc and hence we have to support covering of multiple lines i.e. we will
call your method for each line to get the extent.


Comment 19 Dani Megert CLA 2005-04-26 07:52:02 EDT
Performance wise it is a big improvement (factors for long lines) but it looks
like the returned bounds are wrong (or at least don't match mine), e.g.
StyledText.getBounds(0,0) returned Rectangle {0, 0, 6, 13} for a document that
contains just one single line with 100 'a's. "my" result is (2, 0). The x value
is always off by 2.
Comment 20 Dani Megert CLA 2005-04-26 09:09:12 EDT
Here are some numbers (code currently assumes start and end on same line) based
on a document with a single line filled with n 'a's. The test calls
TextViewer.reveal(0, i) with i= 0..n.

n= 1000
  old code: 19-20s
  new code: 0.38s

n= 10000
  old code: > 1 hour (killed it)
  new code: 4-5s
Comment 21 Dani Megert CLA 2005-04-26 10:26:48 EDT
Similar code can be found in InformationPresenter, TextViewerHoverManager and
TextConsoleViewer. Those places could profit as well.
Comment 22 Mike Wilson CLA 2005-04-26 10:52:36 EDT
That looks like a massive performance win. We should make sure that this one gets fixed.
Comment 23 Felipe Heidrich CLA 2005-04-26 14:43:25 EDT
Created attachment 20379 [details]
screeshot

Okay, I'm supposing IRegion is some kind of SWT Region, and I'm also supposing
you add the bounds of each character from start to end to this region. If I'm
right then we are changing the behaviour of TextViewer.getExtent(). The reason
is because TextLayout#getBounds() returns of bounding box. See the screenshot,
old region is what I believe is the old behaviour, new region is the result if
we change the code.
Comment 24 Felipe Heidrich CLA 2005-04-26 14:52:23 EDT
I should add, the screeshot I'm getting the bounds from 3 to 7 of the 
string "Latin\u05E9\u05E0\u05D1\u05D2\u05E7".

About the code on comment 17, sorry, I forgot to translate from TextLayout 
coordinates to StyledText. After the line:
Rectangle result = layout.getBounds(start - lineOffset, end - lineOffset);
You should add:
result.x += leftMargin - horizontalScrollOffset;
result.y = lineStart * lineHeight - verticalScrollOffset;
result.height = lineHeight;


Comment 25 Dani Megert CLA 2005-04-26 15:06:13 EDT
Here's what our code does. Since we call getLocationAtOffset for each offset and
compute min and max, it should give the same result, shouldn't it?

int left= fTextWidget.getLocationAtOffset(start).x;
int right= left;
for (int i= start +1; i <= end; i++) {
	int x= fTextWidget.getLocationAtOffset(i).x;
	if (left > x)
		left= x;

	if (right < x)
		right= x;
}
return  new Region(left, right - left);
Comment 26 Felipe Heidrich CLA 2005-04-26 15:19:42 EDT
Okay good, I supposed wrong. The only thing we should take care is that end is 
excluded in TextViewer#getExtent and in TextLayout#getBounds it is included.
Comment 27 Dani Megert CLA 2005-04-26 15:25:51 EDT
Sounds good. I already rewrote getExtent(...) so that it can also handle
start/end covering more than one line.
Comment 28 Dani Megert CLA 2005-04-27 03:21:01 EDT
Felipe couldn't your API allow to cover more than one line? There are two
reasons that speak for that:
1) I found at least four places in the SDK and they will have to write the same
helper code to handle this
2) just guessing: due to more data SWT might be able to implement this with
better performance than clients can
Comment 29 Felipe Heidrich CLA 2005-04-27 11:16:22 EDT
> Felipe couldn't your API allow to cover more than one line? 
Yes, I will implement like this anyway. Just keep in mind that if start and 
end are not in the same line the API will returns a big rectangle including 
the bounds of all the lines involved.
Comment 30 Dani Megert CLA 2005-04-27 11:54:48 EDT
The big rectangle is what we compute, so it's perfect.
Comment 31 Felipe Heidrich CLA 2005-04-27 15:37:50 EDT
Daniel, when the range spans over several lines, what do you expect in 
rectangle.width ?
1) the width of the clientArea.width - margins
2) the max width of all lines included in the range 
Comment 32 Dani Megert CLA 2005-04-27 18:24:18 EDT
Since we currently call fTextWidget.getLocationAtOffset(...) for every single
character and compute min and max I'd say this doesn't include the margins.
Comment 33 Felipe Heidrich CLA 2005-04-29 17:46:18 EDT
I added the new API, it is called StyledText#getTextBounds (int start, int 
end), the rules for start and end are the same from StyledText#getText(int 
start, int end).
I know I have some bugs in TextLayout on Windows (the rectangle is too big 
sometimes - when reordering occurs), I'll be fixing it early next week.

Fixed in HEAD > 20050429
Comment 34 Dani Megert CLA 2005-05-02 10:57:23 EDT
I've changed all locations in Platform/JDT Text to use the new API (>=
I200505030010) and filed a bug against Platform Debug where the new API should
be used.