Bug 516281 - SWT.VIRTUAL Tree is not truly virtual - it eagerly pre-creates all items - making it very slow
Summary: SWT.VIRTUAL Tree is not truly virtual - it eagerly pre-creates all items - ma...
Status: RESOLVED MOVED
Alias: None
Product: Platform
Classification: Eclipse Project
Component: SWT (show other bugs)
Version: 4.6   Edit
Hardware: PC Windows NT
: P3 enhancement (vote)
Target Milestone: ---   Edit
Assignee: Platform-SWT-Inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords: triaged
Depends on:
Blocks: 531051
  Show dependency tree
 
Reported: 2017-05-06 16:55 EDT by Espinosa CZ CLA
Modified: 2023-03-20 10:18 EDT (History)
5 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Espinosa CZ CLA 2017-05-06 16:55:13 EDT
SWT.VIRTUAL Tree is not fully virtual and that was making it slow with higher number of rows, higher then 10000.

I modified Snippet047VirtualLazyTreeViewer slightly to have just root elements, no children and started to increase number of root elements from default 10 up; 1000s still good, with 10,000+ it started to be noticeably laggy, with just 40.000 rows it took around 25sec to display.

After some poking around it was obvious that viewer.setChildCount(element, length) is the culprit: the Tree method setChildCount() is incredibly slow.

After some more digging I found that it eagerly pre-creates all items.
All down to this piece of code in org.eclipse.swt.widgets.Tree in method setItemCount (.....):

	for (int i=itemCount; i<count; i++) {
		if (expanded) ignoreShrink = true;
		createItem (null, hParent, OS.TVI_LAST, 0);
		if (expanded) ignoreShrink = false;
	}

Currently on lines 4217 - 4221.

I would expect from a truly virtual table to allocate only necessary number of items from the OS, necessary to cover all displayed rows, plus some buffer, not all 40000 rows in my case as that what it does now. Those items should be reused as user scrolls down. The ChildCount should serve just to set bounds, mainly to set vertical scroll bars correctly.

I am giving NatTable try but this should be addressed for a stock SWT Tree as well.
Comment 1 Leo Ufimtsev CLA 2017-05-08 11:09:31 EDT
Is this specific to Windows?
Could you attach your modified snippet here, I could test if the issue occurs on Gtk..
Comment 2 Espinosa CZ CLA 2017-05-10 07:32:37 EDT
Hi Leo, thank you for looking into it. Yes, so far I tested only on Windows.

I simplified Snippet047VirtualLazyTreeViewer even more, now I have only root elements, just one level in the model. Here is the code I use:


package org.bitbucket.espinosa.efm.playground;

import org.eclipse.jface.viewers.ILazyTreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

/**
 * A simple  SWT.VIRTUAL TreeViewer to test performance with higher number of nodes.
 * Only one level tree, all nodes are root nodes, so it is table like tree.
 * It is adapted, simplified, version of Snippet047VirtualLazyTreeViewer from
 * https://github.com/vogella/eclipse.platform.ui/blob/master/examples/org.eclipse.jface.snippets/Eclipse%20JFace%20Snippets/org/eclipse/jface/snippets/viewers/Snippet047VirtualLazyTreeViewer.java
 */
public class VirtualLazyTreeViewer2 {
	// in my experience:
	// NUMBER_OF_ROOT_NODES = 1_000  is OK application starts nerly instanly
	// NUMBER_OF_ROOT_NODES = 10_000  things starting to get choppy, visible lag when starting 
	// NUMBER_OF_ROOT_NODES = 40_000  start is very slow, up to 25sec, scrolling can be slow, 
	//                                all app reactions are sluggish, even closing the app takes time
	public static final int NUMBER_OF_ROOT_NODES = 40_000;

	private class MyContentProvider implements ILazyTreeContentProvider {
		private TreeViewer viewer;
		private IntermediateNode[] elements;

		public MyContentProvider(TreeViewer viewer) {
			this.viewer = viewer;
		}

		public void dispose() {}

		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			this.elements = (IntermediateNode[]) newInput;
		}

		public Object getParent(Object element) {
			return elements;
		}

		public void updateChildCount(Object element, int currentChildCount) {
			int length;
			if (element instanceof Object[]) {
				// called first, number of root elements (and we don't have any other)
				length = ((Object[])element).length; 
			} else {
				// this is supposed for leaves but since they cannot be expanded 
				// it never steps here
				length = 0; 
			}
			viewer.setChildCount(element, length);
		}

		public void updateElement(Object parent, int index) {
			Object element;
			element =  elements[index];
			viewer.replace(parent, index, element);
			viewer.setChildCount(element, -1); // is this necessary?
		}
	}

	public class IntermediateNode {
		public int counter;

		public IntermediateNode(int counter) {
			this.counter = counter;
		}

		public String toString() {
			return "Node " + this.counter;
		}
	}

	public VirtualLazyTreeViewer2(Shell shell) {
		final TreeViewer v = new TreeViewer(shell, SWT.VIRTUAL | SWT.BORDER);
		v.setLabelProvider(new LabelProvider());
		v.setContentProvider(new MyContentProvider(v));
		v.setUseHashlookup(true); // important! if removed things get even much slower
		IntermediateNode[] model = createModel();
		v.setInput(model);
	}

	private IntermediateNode[] createModel() {
		IntermediateNode[] elements = new IntermediateNode[NUMBER_OF_ROOT_NODES];
		for (int i = 0; i < NUMBER_OF_ROOT_NODES; i++) {
			elements[i] = new IntermediateNode(i);
		}
		return elements;
	}

	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = new Shell(display);
		shell.setLayout(new FillLayout());
		new VirtualLazyTreeViewer2(shell);
		shell.open();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}
		display.dispose();
	}
}
Comment 3 Leo Ufimtsev CLA 2017-05-10 10:59:28 EDT
(In reply to Espinosa CZ from comment #2)
> Hi Leo, thank you for looking into it. Yes, so far I tested only on Windows.

Ok, so I've done some research on this business. I have a mixed conclusion:

Overview:
- I tested with Fedora Gnome Linux Gtk2 & 3.22. (Versions don't seem to make any difference).
- Observations:
  Using jFaceSnippet: [1]
  -- 40 parents * 1000 children each performs at 7 seconds load. Test: [1]
  -- 40,000 parents * 1 child each performs at 14 seconds load. Test: [1]
  
  Using SWT Snippet202 (Virtual). w/ setItemCount(40000)
  -- Start time is around 15 seconds.
  
  Using SWT Snippet15 (edited so that 40,000 parents & 1 child each is created)
  -- Start time is around 1:38 minutes.

Conclusion:
- Based on the above (Snippet202 vs Snippet15), using "Virtual" speeds up performance by 7x times. But it's not 'instant'.
- Having few parents and many children seems to be faster than having many single parents.
-> In the long run, we should probably investigate if there's a way to make this quicker.

I noticed there are a number of related bugs:
Bug 129457 – Virtual Tree very slow 
Bug 113526 – Tree.createItem() has bad performance 


[1] JFace Snippet047VirtualLazyTreeViewer with slight modification:

	private IntermediateNode[] createModel() {
		IntermediateNode[] elements = new IntermediateNode[40000];   // 10->40000   #parent count.
		for (int i = 0; i < 40000; i++) {			     // 10->40000
			elements[i] = new IntermediateNode(i);
			elements[i].generateChildren(2);		     // 1000->2	    #child count.
		}
		return elements;
	}
Comment 4 Leo Ufimtsev CLA 2017-05-10 11:01:16 EDT
I.e, virtual does seem to be virtual, but virtual is not as fast as one would expect.
Comment 5 Eric Williams CLA 2017-05-10 15:07:32 EDT
This is an interesting bug. I can only speak for GTK, but here is some insight re: SWT.VIRTUAL trees.

AFAIK, GTK3 doesn't actually support lazy loading in GtkTreeView. The argument is that the only items that require computation is the items that are shown, i.e. those that are visible to the user. The computation/"time" that comes with these items is size calculations, events, etc. There is a "fixed-height-mode" property for GtkTreeView, which when set, allows GTK to assume all rows are the same height. This greatly speeds up things like size computations since the size of every row does not need to be computed.

However in SWT we need to support SWT.VIRTUAL trees so our solution is to just set the "fixed-height-mode" property. In a strict sense this isn't actually true lazy loading but as long as the running time remains within O(1) time than it shouldn't be an issue. It would be interesting to do some more testing to better analyse the performance on GTK3.
Comment 6 Vasili Gulevich CLA 2023-03-20 10:18:57 EDT
Moved to https://github.com/eclipse-platform/eclipse.platform.ui/issues/649