Bug 129457 - Virtual Tree very slow
Summary: Virtual Tree very slow
Status: CLOSED WONTFIX
Alias: None
Product: Platform
Classification: Eclipse Project
Component: SWT (show other bugs)
Version: 3.2   Edit
Hardware: PC Windows XP
: P3 normal with 18 votes (vote)
Target Milestone: ---   Edit
Assignee: Felipe Heidrich CLA
QA Contact:
URL:
Whiteboard: stalebug
Keywords:
: 149639 207342 (view as bug list)
Depends on:
Blocks:
 
Reported: 2006-02-25 13:06 EST by Rutger Ovidius CLA
Modified: 2021-07-01 02:40 EDT (History)
32 users (show)

See Also:


Attachments
Perf test case for setItemCount() (4.21 KB, application/octet-stream)
2007-08-27 11:40 EDT, Irwin CLA
no flags Details
testcase (3.48 KB, application/octet-stream)
2010-04-07 12:54 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 Rutger Ovidius CLA 2006-02-25 13:06:20 EST
3.2M5  3224  win32.

I have altered Snippet 144 to show both a virtual table and tree. 

The COUNT variable was reduced from 1 million or it would never complete on the tree.

The virtual tree is incredibly slow. 
Table: 70ms
Tree: 6810ms

Further, scrolling up and down in the table is smooth. In the tree it is flickering and slow.


public class Snippet144 {

  static final int COUNT = 20000;
  
  public static void main(String[] args) {
    Display display = new Display();
    final Shell shell = new Shell(display);
    shell.setLayout(new RowLayout(SWT.VERTICAL));
    final Table table = new Table(shell, SWT.VIRTUAL | SWT.BORDER | SWT.FULL_SELECTION);
    final Tree tree = new Tree(shell, SWT.VIRTUAL | SWT.BORDER | SWT.FULL_SELECTION);
    
    table.addListener(SWT.SetData, new Listener() {
      public void handleEvent(Event event) {
        TableItem item = (TableItem) event.item;
        int index = table.indexOf(item);
        item.setText("Item " + index);
        System.out.println("TABLE: " + item.getText());
      }
    });
    
    tree.addListener(SWT.SetData, new Listener() {
      public void handleEvent(Event event) {
        TreeItem item = (TreeItem) event.item;
        int index = tree.indexOf(item);
        item.setText("Item " + index);
        System.out.println("TREE: " + item.getText());
      }
    });
    
    table.setLayoutData(new RowData(200, 200));
    tree.setLayoutData(new RowData(200, 200));
    
    Button button = new Button(shell, SWT.PUSH);
    button.setText("Add Items");
    final Label label = new Label(shell, SWT.NONE);
    final Label label2 = new Label(shell, SWT.NONE);
    
    button.addListener(SWT.Selection, new Listener() {
      public void handleEvent(Event event) {
        long t1 = System.currentTimeMillis();
        table.setItemCount(COUNT);
        long t2 = System.currentTimeMillis();
        label.setText("TABLE Items: " + COUNT + ", Time: " + (t2 - t1) + " (ms)");
        
        t1 = System.currentTimeMillis();
        tree.setItemCount(COUNT);
        t2 = System.currentTimeMillis();
        label2.setText("TREE Items: " + COUNT + ", Time: " + (t2 - t1) + " (ms)");
        
        
        shell.layout();
      }
    });
    
    shell.pack();
    shell.open();
    System.err.println(SWT.getVersion());
    while (!shell.isDisposed()) {
      if (!display.readAndDispatch())
        display.sleep();
    }
    display.dispose();
  }
}
Comment 1 Steve Northover CLA 2006-02-27 19:02:48 EST
There is a hard limit on Windows around 32767 when adding items to a single level of a tree gets very slow.  There's not much I can do about it.
Comment 2 Rutger Ovidius CLA 2006-02-27 20:27:41 EST
Even with an item count less than 32767 there is quite a big difference.

setItemCount:

2500 items: Table: 70ms, Tree: 260ms     
5000 items: Table: 70ms, Tree: 639ms
10000 items: Table: 70ms, Tree: 3526ms
20000 items: Table: 70ms, Tree: 11528ms
30000 items: Table: 70ms, Tree: 15014ms
32700 items: Table: 70ms, Tree: 18290ms
40000 items: Table: 70ms, Tree: 27291ms


20000 < 32767
Comment 3 Ivan Bojer CLA 2006-04-03 17:38:58 EDT
I believe that this is exact problem as BUG #113526. I ran some tests and here is a comparison between Linux (FC4) and windoze:

Calling setItemCount on a virtual tree with number of nodes "n" yields:

#of nodes    Windows     Linux (FC4)

10000          8s         0s
20000         29s         3s
30000         63s         7s
40000        103s        16s
50000        251s        27s
100000        -         127s

...because framework spends most of the time in Tree.createItem(...) method allocating space for a tree nodes.
Comment 4 Steve Northover CLA 2006-07-05 11:09:19 EDT
*** Bug 149639 has been marked as a duplicate of this bug. ***
Comment 5 Steve Northover CLA 2006-09-26 14:09:26 EDT
See http://support.microsoft.com/default.aspx?scid=kb;en-us;Q182231 for a description of the problem from the MSDN.
Comment 6 Irwin CLA 2007-08-24 19:24:00 EDT
I think there s a more serious issue. Even once the tree is built, adding a single item takes a lot of time (linear in number of elements) because of setItemCount()

which takes a time linear to the number of entries to update the item count because of this code :

                while (hItem != 0 && itemCount < count) {
                        hItem = OS.SendMessage(handle, OS.TVM_GETNEXTITEM,
OS.TVGN_NEXT, hItem);
                        itemCount++;


it looks like it tries to find the last item of the tree by starting from the root and then scanning the whole tree. Can't we make that more efficient ? (like caching the last item ? )

see BUG 200214
Comment 7 Steve Northover CLA 2007-08-24 20:54:32 EDT
Can you prove that this loop is causing the problem?  There is a cache that Tree.findIndex() and Tree.findItem() that could also be used in this case.  These methods return the index or the item and keep track of the last index and HTREEITEM.

It's a bit tricky because Tree.setItemCount() method needs both the count and the last HTREEITEM in case it needs to delete items instead of adding them but it is doable.  The code is quite delecate and I'd like to make sure it makes a difference before changing it.
Comment 8 Irwin CLA 2007-08-27 11:07:52 EDT
Yes I confirm the bottleneck is in this loop.
I timed with a 75K items tree (under XP), and adding an element through the viewer.add() API takes 150 ms.
That s exactly the time it takes to go over this loop (75k iterations):

		while (hItem != 0 && itemCount < count) {
			hItem = OS.SendMessage(handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem);
			itemCount++;
		}


Let me know if there s a patch/fix you want me to test (to leverage the cache you mentionned).

Comment 9 Steve Northover CLA 2007-08-27 11:18:57 EDT
Please attach the benchmark code.
Comment 10 Irwin CLA 2007-08-27 11:40:54 EDT
Created attachment 77040 [details]
Perf test case for setItemCount()

Run the test case, it will create a virtual tree with high number of entries.
Then press the 'enter' key in the console to add an element.
Put some timing around the Tree.setItemCount() loop code :

		long start = System.currentTimeMillis();
		while (hItem != 0 && itemCount < count) {
			hItem = OS.SendMessage(handle, OS.TVM_GETNEXTITEM, OS.TVGN_NEXT, hItem);
			itemCount++;
		}
		System.err.println("setItemCount " + (System.currentTimeMillis() - start));

And you will see that this loop takes around 100 ms to complete.
Comment 11 Steve Northover CLA 2007-08-27 12:59:58 EDT
My initial investigation shows that the optimization has no effect for 5000 items.  I'm still looking into it to make sure that the caching code is running.
Comment 12 Irwin CLA 2007-08-27 13:45:49 EDT
(In reply to comment #11)
> My initial investigation shows that the optimization has no effect for 5000
> items.  I'm still looking into it to make sure that the caching code is
> running.
> 
Depending on how fast your machine is, 5000 may not show much improvement.
Try it with at least 50 000.  (when I run 5000 items, I get 0 or 16 ms latency, which is probably due to context switching)
Comment 13 Irwin CLA 2007-09-04 16:03:51 EDT
Steve, any luck ? 
Do you have anything i can test ? 


Comment 14 Steve Northover CLA 2007-10-25 16:18:50 EDT
*** Bug 207342 has been marked as a duplicate of this bug. ***
Comment 15 Lubomir Marinov CLA 2008-03-24 16:07:46 EDT
I20080207-1530

Steps to reproduce:
The following time statistics have been acquired from a Tree loaded with tens of thousands of TreeItems, with a relatively small number of TreeItems parented in a single TreeItem (20 at most), and approximately 2/3 of them being expanded. The resulting times for each method are rounded to 65,000 invocations. The comparison is of the performance of the respective methods of Tree and TreeItem in the cases of Tree without and with the SWT.VIRTUAL flag. The time statistics are averages of 5 runs of each case, the "without" and the "with" runs are run one after the other once, then the cycle is repeated again in order to minimize the effects of the differences in the execution environment.

What happens:
                             Windows             Mac OS X
                         without   with        without   with

Tree.createItem            854     1439          111     123
TreeItem.setBackground     34      51            368     412
TreeItem.setForeground     3       8             4       5
TreeItem.setImage          598     827           19      31
TreeItem.setText           273     472           93      116

Mac OS X seems to be pretty fast in the described scenario and doesn't seem to show a noticeable difference between running without and with the SWT.VIRTUAL flag.

However, the SWT Tree on Windows seems to perform noticeably slower in SWT.VIRTUAL mode.

What should happen:
The understanding is that SWT.VIRTUAL should allow for noticeably better performance on large hierarchies.

I've been looking at the mentioned methods and I've been trying to understand the reason for SWT.VIRTUAL being slower and I'm adding this comment because I don't really see that much of a difference in the code of the said methods.
Comment 16 Simon Chemouil CLA 2009-10-14 09:44:43 EDT
I confirm the bug here.

When there are "many" (from 1000 to 10000, that's far less than 2^15-1):

(1) In virtual mode (Win32)
- The scrollbar SWT Virtual trees under Win32 is barely usable
- Selecting items in the node lags seconds behind the actual click
- Navigating with the keyboard is nearly impossible


(2) Non Virtual mode (Win32)
- The scrollbar behaves perfectly fine (very well in fact)
- Selecting items in the node still lags seconds behind the actual click
- Navigating with the keyboard is still nearly impossible (probably because of the selection)

The main difference between (1) and (2) is the time required to open the parent node (obviously way faster with 1).


Those problems are inexistent when running GNU/Linux + GTK.


Now, from my non-scientific tests, snooping around and trying to get performance out of the tree widget for a few days, my idea is that there are two distinct problems:
- the selection problem, under Win32, which has to do with bad random access performances in a node with many children (ie, selecting the nth children quickly)
- the scrollbar problem is distinct and only happens in virtual mode, I believe it has to do with the cost of creating the TreeItems while scrolling (maybe Windows doesn't like that in its UI thread, but then again I have no knowledge of Win32).


You got my vote for this bug.
Comment 17 Felipe Heidrich CLA 2009-10-16 12:23:44 EDT
(In reply to comment #13)
> Steve, any luck ? 
> Do you have anything i can test ?

Hi Irwin, Steve is no longer working here.
As much as I can I'll be working on the bugs that are assigned to him.

For while loop you refer to in comment 8, I removed the entire loop (my testcase never calls setItemCount to decrease the number of items). I got no performance improvement removing the loop. The time is not there.
Comment 18 Simon Chemouil CLA 2010-04-02 09:00:30 EDT
Is there any news for that bug, any chance to get it fixed for Helios or very soon thereafter? It is a major blocker for our application.
Comment 19 Felipe Heidrich CLA 2010-04-07 12:54:58 EDT
Created attachment 164096 [details]
testcase

I don't have a fix for this problem.

In win32, Table (ListView) has native support for virtual (see LVS_OWNERDATA and LVM_SETITEMCOUNT) does not. Tree (Tree-View) does not have the same support.

Try the attachemnt, see that an unsubclassed TreeView is just as slow as the SWT Tree. All the time is in TVM_INSERTITEM, which we are forced to call.
Comment 20 jan CLA 2010-11-01 15:22:20 EDT
Hi, 

unfortunately I met the same problem as you are describing in this forum.

In the last comment there is reference to MSDN

I am newbie in SWT but I think there is possibility to implement virtual tree with this flag I_CHILDRENCALLBACK.

Maybe you can see the following article http://www.codeguru.com/cpp/cpp/cpp_mfc/callbacks/article.php/c16667

Is it possible to use this callback technique in SWT tree?

Thanks,
Jan
Comment 21 Sebastian Bauer CLA 2011-04-07 06:25:58 EDT
I also find the virtual tree very slow in Linux/GTK not only for Windows. If I increase the item count of Snippet202 to say 20000, it needs about 10 seconds for the window coming up.
Comment 22 Yang Lu CLA 2012-02-28 13:31:09 EST
I find the virtual tree very slow in SWT3.7
I dynamic change setItemCount, performance has improved.
-------------------------------------------------------------------------
package com.swt.example;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.TreeAdapter;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
/**
 * @author LuYang
 * www.suntodo.com
 */
public class TestTreeVirtual extends Shell {
	
	public static int TEST_COUNTS = 100000;
	/**
	 * Launch the application.
	 * @param args
	 */
	public static void main(String args[]) {
		try {
			Display display = Display.getDefault();
			TestTreeVirtual shell = new TestTreeVirtual(display);
			shell.open();
			shell.layout();
			while (!shell.isDisposed()) {
				if (!display.readAndDispatch()) {
					display.sleep();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Create the shell.
	 * @param display
	 */
	public TestTreeVirtual(Display display) {
		super(display, SWT.SHELL_TRIM);
		setLayout(new FillLayout(SWT.HORIZONTAL));
		
		Composite composite = new TreeVirtualDynamicExample(this, SWT.NONE);
		createContents();
	}

	/**
	 * Create contents of the shell.
	 */
	protected void createContents() {
		setText("SWT Application");
		setSize(450, 300);

	}

	@Override
	protected void checkSubclass() {
		// Disable the check that prevents subclassing of SWT components
	}
}

class TreeVirtualDynamicExample extends Composite{
	private Tree tree;
	public static String TREEITEM_LOADING = "TREEITEM_LOADING";
	/**
	 * Create the composite.
	 * @param parent
	 * @param style
	 */
	public TreeVirtualDynamicExample(Composite parent, int style) {
		super(parent, style);
		setLayout(new GridLayout(1, false));
		
		tree = new Tree(this, SWT.BORDER | SWT.VIRTUAL);
		tree.setLinesVisible(true);
		tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

		// create Virtual Tree	
		String root1 = new String("Root1");
		String root2 = new String("Root2");		
		createTree(tree, new Object[]{root1,root2});
	}
	
	public void createTree(final Tree tree, final Object[] roots) {

		class TreeSetDataListener implements Listener {
			
	        public void handleEvent(Event event) {	
				TreeItem currentItem = (TreeItem)event.item;
				TreeItem parentItem = currentItem.getParentItem();

				final Object child;
				Object[] currentAndBrother = null;
				int currentItemIndex;// 
				int currentIndex = 0;//

				if (parentItem == null) {// root Item
					currentAndBrother = (Object[]) tree.getData();
					currentItemIndex  = tree.indexOf(currentItem);
					currentIndex = currentItemIndex;
					child = currentAndBrother[currentIndex];
					createOwnLoadingItem(currentItem);
					
				}else{// not root node
					Object data = (Object) parentItem.getData();
					
					// loading Item
					if(data!=null && data.equals(TREEITEM_LOADING)){
						currentItem.setText("loading");
						child = null;

					// a Item	
					}else if(data!=null && data instanceof Object[]){
						currentAndBrother =(Object[]) data;
						currentItemIndex  = parentItem.indexOf(currentItem);
						currentIndex  = currentItemIndex-1;
						// check currentIndex
						if(currentIndex>=0 && currentIndex<currentAndBrother.length)
							child = currentAndBrother[currentIndex];
						else
							child = null;
						createOwnLoadingItem(currentItem);

					}else{
						child = null;
					}
				}
				
				if(child!=null) {
					currentItem.setText(getText(child));

					TreeItem belongLoading = getBelongLoadingItem(currentItem);
					updateLoadingItem(currentItem, currentIndex-1, currentAndBrother, belongLoading);
					disposeLoadingItem(currentItem, currentIndex-1, currentAndBrother, belongLoading);
			
					if(parentItem!=null && parentItem.getItemCount() <= currentAndBrother.length){
						parentItem.setItemCount(parentItem.getItemCount()+1);
					}					
				}
	        }
	    }		

		class TreeListener extends TreeAdapter {
			@Override
			public void treeExpanded(TreeEvent e) {		
				TreeItem currentItem = (TreeItem) e.item;				
				createChildren(currentItem);
			}
		}
		tree.addListener(SWT.SetData, new TreeSetDataListener());
		tree.addTreeListener(new TreeListener());	
		
		// create virtual Tree					
		createRoots(tree, roots);		
	}

	public void createRoots(Tree tree, Object[] roots) {
		tree.setData(roots);	
		tree.setItemCount(roots.length);		
	}
	
	public void createChildren(final TreeItem currentItem){	
		
		final Object child = currentItem.getData();

		Thread thread = new Thread() {
			public void run() {
				if (tree.isDisposed()) return;
				final Object[] children = getChildren(child);		
				
				Display.getDefault().asyncExec(new Runnable () {
				//Display.getDefault().syncExec(new Runnable() {
					public void run() {
						if(!currentItem.isDisposed()){
							//currentItem.clearAll(true);
							currentItem.setData(children);
							currentItem.setItemCount(currentItem.getItemCount()+1);// display + 
						}
					};												
				});
			};
		};
		thread.start();
	}

	public TreeItem createOwnLoadingItem(TreeItem currentItem) {
		TreeItem ownLoading=null;
		if(currentItem.getItemCount()==0){
			//currentItem.setItemCount(1);
			ownLoading = new TreeItem(currentItem, 0);
			ownLoading.setText("loading...");
		}
		return ownLoading;
	}
	
	public TreeItem getBelongLoadingItem(TreeItem currentItem) {
		TreeItem loading = null;
		if(currentItem.getParentItem()!=null){
			loading=currentItem.getParentItem().getItems()[0];
			if(loading!=null && loading.getData()!=null) {
				if(loading.getData().equals(TREEITEM_LOADING))
					return loading;
			}
		}			
		return loading;
	}

	public void updateLoadingItem(TreeItem currentItem, int currentIndex, Object[] currentAndBorther, TreeItem belongLoading){
		if(belongLoading!=null && !belongLoading.isDisposed())
			belongLoading.setText((currentIndex+1)+"/"+currentAndBorther.length);
	}	
	
	public void disposeLoadingItem(TreeItem currentItem, int currentIndex, Object[] currentAndBorther, TreeItem belongLoading){
		if(belongLoading!=null && !belongLoading.isDisposed() && currentIndex+1==currentAndBorther.length){
			belongLoading.dispose();
		}			
	}	
	
	public Object[] getChildren(Object parent){
		String[] children = new String[TestTreeVirtual.TEST_COUNTS];
		for (int i = 0; i < children.length; i++) {
			children[i] = "child"+"-"+i;
		}
		return children;
	}

	public String getText(Object element){
		return element.toString();
	}
	
	@Override
	protected void checkSubclass() {
		// Disable the check that prevents subclassing of SWT components
	}
	
	public Tree getTree() {
		return tree;
	}

}
Comment 23 Henno Vermeulen CLA 2015-10-26 10:59:45 EDT
Unfortunately almost 10 years later I am still seeing this same performance issue in our Eclipse RCP application using Windows 8 and the (somewhat outdated) SWT version 3.7.2.

I am using an SWT Tree through the JFace TreeViewer.

Populating a non-virtual tree with 4000 root items takes about 1 second which is even longer than the delay caused by our database query! This is rather annoying for a good user experience.

This issue can easily be reproduced by copy/pasting Snippet002TreeViewer and adjusting the number of nodes.

I thought SWT.VIRTUAL was supposed to address this issue, but similar to what is seen in Comment 15, the tree now takes about 50% longer to create. (I copy/pasted/adjusted Snippet047VirtualLazyTreeViewer).

Are there any known ways of improving this?

I found a workaround that may be (barely) acceptable for user experience: creating a single "All items" root node and adding the 4000 tree items. This tree can be created in about 10ms instead of 1000ms. Expanding the root node takes around 500ms instead of 1000ms.
Perhaps creating artificial categories (A, B, C .. Z) can help even more.
Comment 24 Henno Vermeulen CLA 2015-10-27 04:48:18 EDT
I ran the altered Snippet144.

It's interesting that 10 years later on a very high end pc, the virtual table takes about the same time, even more: 7090ms. The table now takes 20ms.

Is this some kind of Windows task scheduling policy that has never changed?
Comment 25 Henno Vermeulen CLA 2015-10-27 05:25:29 EDT
(In reply to Henno Vermeulen from comment #24)
Correction, the virtual tree takes 7090ms. (Virtual tables are very fast in my experience)
Comment 26 Henno Vermeulen CLA 2015-10-27 06:34:16 EDT
I can confirm that the bottleneck is in the OS.SendMessage call of type TVM_INSERTITEM as mentioned in Comment 19.

It's in this line in Tree.createItem:

hNewItem = OS.SendMessage(handle, OS.TVM_INSERTITEM, 0, tvInsert);

Creating 5000 tree items takes 1103ms of which 1024ms is spent in this line of code.
Comment 27 Henno Vermeulen CLA 2015-10-27 08:02:19 EDT
I investigated this deeper and I'm afraid there's not much there can be done except looking for alternatives, such as:

- other technology such as Nebula nattable or use Swing's JTree
- reduce the number of children per node by adding extra tree nodes. Even putting all items below a single root node seems to help a bit.
- reusing the same Tree instead of destroying and rebuilding it.
- when using JFace's TreeViewer and setting different input, perhaps the SWT Tree could internally keep existing tree items around and reuse them for the new input instead of destroying + re-adding them


Details:
The following line in Tree.createItem is the culprit:

hNewItem = OS.SendMessage(handle, OS.TVM_INSERTITEM, 0, tvInsert);

The tvInsert structure contains a hInsertAfter handle to the item after which the new item is to be inserted.

According to MSDN's documentation on TVINSERTSTRUCT
https://msdn.microsoft.com/en-us/library/windows/desktop/bb773452(v=vs.85).aspx

"Important: Using TVI_LAST to insert an item into a tree-view node that already contains a large number of items can take a long time, causing the application to stop responding until the insert operation completes."

So I tried a few alternatives: TVI_ROOT, the previous handle, the first handle, and TVI_FIRST. Using the first handle or TVI_FIRST takes about 10% less time for 5000 nodes and about 30% for 20000 nodes. It is something but not the performance increase I was hoping for.

According to this blog http://www.virtualdub.org/blog/pivot/entry.php?id=184 the Win32 tree view internally stores its nodes as a singly-linked list and adding an item to the end takes linear time according to the number of items. It suggests to add nodes in reverse order.

When inserting in the front, the time doesn't drastically decrease so there must still be something else going on why the OS call is so slow.
Comment 28 Eclipse Genie CLA 2021-07-01 02:40:50 EDT
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet. As such, we're closing this bug.

If you have further information on the current state of the bug, please add it and reopen this bug. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant.

--
The automated Eclipse Genie.