Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 332857

Summary: CompositeNode.getTotalOffset too slow
Product: [Modeling] TMF Reporter: Mark Christiaens <mark.g.j.christiaens>
Component: XtextAssignee: Project Inbox <tmf.xtext-inbox>
Status: CLOSED FIXED QA Contact:
Severity: normal    
Priority: P3 CC: mark.g.j.christiaens, sebastian.zarnekow, sven.efftinge
Version: 2.0.0Flags: sven.efftinge: indigo+
Target Milestone: M5   
Hardware: PC   
OS: Linux   
Whiteboard:

Description Mark Christiaens CLA 2010-12-17 10:19:34 EST
Build Identifier: I20101028-1441

Working with the daily build of Xtext 2.0.

When opening a big VHDL file (> 2 MB) and working around a number of performance issues already reported, my file does not open.  The Xtext editor is busy in CompositeNode.getTotalOffset

Name	Time (ms)	Invocation Count
...
org.eclipse.xtext.ui.editor.model.XtextDocument.readOnly(IUnitOfWork)	264052	1
org.eclipse.xtext.util.concurrent.AbstractReadWriteAcces.readOnly(IUnitOfWork)	264052	1
org.eclipse.xtext.ui.editor.folding.DefaultFoldingRegionProvider$1.exec(Object)	264052	1
org.eclipse.xtext.ui.editor.folding.DefaultFoldingRegionProvider$1.exec(XtextResource)	264052	1
org.eclipse.xtext.ui.editor.folding.DefaultFoldingRegionProvider.doGetFoldingRegions(IXtextDocument, XtextResource)	264052	1
org.eclipse.xtext.ui.editor.folding.DefaultFoldingRegionProvider.addFoldingRegions(IXtextDocument, EObject, List)	264047	258
org.eclipse.xtext.ui.editor.folding.DefaultFoldingRegionProvider.getPosition(IXtextDocument, ICompositeNode)	264044	258
org.eclipse.xtext.nodemodel.impl.AbstractNode.getOffset()	132059	516
org.eclipse.xtext.nodemodel.impl.CompositeNode.getTotalOffset()	132055	22
org.eclipse.xtext.nodemodel.impl.CompositeNode.getTotalOffset()	132055	22
org.eclipse.xtext.nodemodel.util.NodeTreeIterator.hasNext()	99855	25857356
org.eclipse.xtext.nodemodel.util.NodeTreeIterator.next()	32199	25857356
...

The code seems to be executing in the code after the "should never happen" comment.

public int getTotalOffset() {
		if (firstChild != null)
			return firstChild.getTotalOffset();
		CompositeNode compositeWithSiblings = this;
		while(!compositeWithSiblings.hasSiblings() && compositeWithSiblings.basicGetParent() != null) {
			compositeWithSiblings = compositeWithSiblings.basicGetParent();
		}
		if (compositeWithSiblings.basicHasNextSibling()) {
			AbstractNode sibling = compositeWithSiblings.basicGetNextSibling();
			return sibling.getTotalOffset();
		}
		
		// expensive fallback - should never happen in a valid node model
		BidiTreeIterator<INode> iter = getRootNode().getAsTreeIterable().iterator();
		ILeafNode lastSeen = null;
		while(iter.hasNext()) {
			INode next = iter.next();
			if (next == this) {
				if (lastSeen == null)
					return 0;
				else
					return lastSeen.getTotalEndOffset();
			}
			if (next instanceof ILeafNode) {
				lastSeen = (ILeafNode) next;
			}
		}
		return 0;
	}

I suspect that the getTotalOffset method was invoked on the root node of the tree (but I'm not sure)?  I would expect that node not to have siblings or parents which goes into the expensive fallback code.  

I put a "return 0;" before the expensive callback and I could open my big file and the code folds seem to have been constructed correctly.  It still takes a while but that's better than nothing :)


Reproducible: Always
Comment 1 Sven Efftinge CLA 2010-12-17 10:27:29 EST
Thanks Mark. Your detailed feedback is very helpful.
Comment 2 Mark Christiaens CLA 2010-12-17 11:53:12 EST
Just to make sure that you guys are not off on a wild goose chase.  I've instrumented the getTotalOffset function and it seems that most of the time it ends up in the expensive fall back code.  That probably means that it's something else than handling the root node that is causing the problem.  Don't know what though. 

Adding the return 0; line does break some of the navigation (F3) so that's definitely no fix.
Comment 3 Sebastian Zarnekow CLA 2010-12-17 12:29:40 EST
Hi Mark,

the root node will return 0 immediatly (see RootNode#getTotalOffset). I'd assume that your model has siblings but is the last sibling in many cases so basicHasNextSibling() will return false.
I'd hoped that there is no real need to check for basicHasPreviousSibling, too because it requires some more code - one has to ensure that two adjacent nodes do not overflow the stack by subsequent calls to nextSibling and previousSibling.
I'll definitely look into this. Thanks for the valuable report.
Comment 4 Mark Christiaens CLA 2010-12-21 08:12:54 EST
(In reply to comment #3)
> Hi Mark,
> 
> the root node will return 0 immediatly (see RootNode#getTotalOffset). I'd
> assume that your model has siblings but is the last sibling in many cases so
> basicHasNextSibling() will return false.
> I'd hoped that there is no real need to check for basicHasPreviousSibling, too
> because it requires some more code - one has to ensure that two adjacent nodes
> do not overflow the stack by subsequent calls to nextSibling and
> previousSibling.
> I'll definitely look into this. Thanks for the valuable report.

I was looking at this again and I wonder if it's just a question of fixing one if statement (the one that climbs to a node with siblings):

public int getTotalOffset() {
		if (firstChild != null)
			return firstChild.getTotalOffset();
		CompositeNode compositeWithSiblings = this;
		while(!compositeWithSiblings.basicHasNextSibling() && compositeWithSiblings.basicGetParent() != null) {
			compositeWithSiblings = compositeWithSiblings.basicGetParent();
		}
		if (compositeWithSiblings.basicHasNextSibling()) {
			AbstractNode sibling = compositeWithSiblings.basicGetNextSibling();
			return sibling.getTotalOffset();
		}
		// expensive fallback - should never happen in a valid node model
		BidiTreeIterator<INode> iter = getRootNode().getAsTreeIterable().iterator();
		ILeafNode lastSeen = null;
		while(iter.hasNext()) {
			INode next = iter.next();
			if (next == this) {
				if (lastSeen == null)
					return 0;
				else
					return lastSeen.getTotalEndOffset();
			}
			if (next instanceof ILeafNode) {
				lastSeen = (ILeafNode) next;
			}
		}
		return 0;
	}
Comment 5 Sebastian Zarnekow CLA 2010-12-21 08:18:32 EST
Mark,

that should do the trick.
I'll have a second look at it but at a first glance it looks good to me.

Thanks a lot,
Sebastian
Comment 6 Sebastian Zarnekow CLA 2010-12-21 11:27:25 EST
Thanks for the patch. I've pushed it to master.
Comment 7 Karsten Thoms CLA 2017-09-19 17:34:26 EDT
Closing all bugs that were set to RESOLVED before Neon.0
Comment 8 Karsten Thoms CLA 2017-09-19 17:45:39 EDT
Closing all bugs that were set to RESOLVED before Neon.0