Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major scaling/performance issues with node.addChildren() #708

Closed
aleventhal opened this issue Mar 22, 2017 · 12 comments
Closed

Major scaling/performance issues with node.addChildren() #708

aleventhal opened this issue Mar 22, 2017 · 12 comments

Comments

@aleventhal
Copy link
Contributor

aleventhal commented Mar 22, 2017

Expected and actual behavior

Expected: fast performance when using node.addChildren()
Actual: performance degrades drastically as the tree fills up. In the case of the automation inspector I'm writing, where I'm using the tree grid for a log that has grouped log items, first the app begins to take 3+ seconds to responds and ultimately rejects user input.

Steps to reproduce the problem

  1. Create a tree with many nodes, 2 levels deep. Add 500 top level nodes, each with 500 child nodes.
  2. Add a child to the root

Environment

  • Browser type and version: Chrome latest
  • jQuery and jQuery UI versions: 3.1.1 and 1.12.1
  • Fancytree version: 2.21.0
    enabled/affected extensions:
    Also using filter and table extensions.

What I learned in debugging the problem

First, thank you for such a great library. The ARIA and accessibility feature are especially important to us at Google. I'm using this to develop a Chrome automation API inspector app. You can see it here: https://github.com/aleventhal/automation-inspector

Here are my observations:

  • The class attribute is rewritten to nodes that have not changed. In my experience, even though it is counterintuitive, this affects performance more than checking to see if the class needs to be rewritten before doing so. I'm not sure how much of a difference it would make but I believe it would help. I haven't tried to implement this.
  • Moreover, every time a node is added to the root, the entire tree is re-rendered. This does not seem necessary. Why not just render the new children? This is probably not the exact right change but it really helped solve my performance issue by a huge amount:

Old code (line 441 of jquery.fancytree.js in addChildren() )

		if( !this.parent || this.parent.ul || this.tr ){
			// render if the parent was rendered (or this is a root node)
			this.render();
		}

New code:

		// --- Begin performance improvement change  ---
		if (!this.parent) {
			// Fast path -- don't render every child of root, just the new ones!
			for(i=0, l=nodeList.length; i<l; i++) {
				nodeList[i].render();
			}
		}
		else if( this.parent.ul || this.tr ){
			// render if the parent was rendered (or this is a root node)
			this.render();
		}
		// --- End performance improvement change  ---
@mar10
Copy link
Owner

mar10 commented Mar 22, 2017

Cool, thanks for the feedback!
I am always very interested in performance improvements and I will try this.
There already is a benchmark suite, in case you didn't notice:
http://wwwendt.de/tech/fancytree/test/unit/test-bench.html

Btw. if you are adding entries in bursts, you could try to temporarily disable updates using tree. enableUpdate().

Would be interesting to see a live demo of your tool.
Since you are talking about using Fancytree for logging: how many nodes (visible and total) are you expect to have max.?
Beyond any optimizations, in my experience browser performance degrades drastically for large tables (event if Fancytree is not involved).

p.s.
Since you seem to be an ARIA expert: if you have suggestions to improve the markup, let me know. I tried to follow the guidelines, but I am not really sure if the current implementation offers the best user experience (on different browsers or screen readers).

@aleventhal
Copy link
Contributor Author

aleventhal commented Mar 23, 2017 via email

@mar10
Copy link
Owner

mar10 commented Mar 24, 2017

Thanks, I will look into it.
It would be good to have a benchmark to compare optimization results.
I tried to create 500x500 nodes (500 nodes top-level, 250k overall), the top-level nodes being collapsed.
I measured ~2-3 seconds. Adding another top-level node took ~20ms.
Even if I did not yet apply your patch, that seems to be faster than the effect you are describing?
I assume, timings may be inacurate, because the JS code is done, but the browser still has not finished rendering. I am going to experiment a bit more...

@aleventhal
Copy link
Contributor Author

aleventhal commented Mar 24, 2017 via email

@mar10
Copy link
Owner

mar10 commented Mar 25, 2017

I added a benchmark to the suite the seems to better simulate the case.
Don't know if this matches your timings better...

It makes a difference, if the nodes are collapsed/unrendered, collapsed/rendered, or expanded. Your optimization seems to work best in the latter case.

	for( var i=0; i<20; i++ ) {
	// 	node.children[i].render(true, true);
		node.children[i].setExpanded(true);
	}

bench_issue_708.pdf

Would you like to make a PR?

Another thing you might try is to remove markup when nodes are collapsed, and see if it makes a difference, when your is used for a while:

	$("#tree").fancytree({
		collapse: function(event, data) {
			data.node.discardMarkup();
		},

@aleventhal
Copy link
Contributor Author

aleventhal commented Mar 31, 2017 via email

@aleventhal
Copy link
Contributor Author

aleventhal commented Mar 31, 2017 via email

mar10 pushed a commit that referenced this issue Apr 2, 2017
* Issue #708. Speedup improvement for addChildren. Only perform necessary rendering

* Slightly less code
@mar10
Copy link
Owner

mar10 commented Apr 2, 2017

I don't think that the test suite covers all use cases, but anyway: looks good to me.
I merged your changes, thanks! Let's see, what happens.
Concerning the

node.render() must be called on the parent.

comment: I believe that was also related to cases where the children list was modified directly. The render() method also takes care to keep the <li> order order in sync with the model's node list, for example.

@mar10
Copy link
Owner

mar10 commented Apr 10, 2017

PR was merged

@mar10 mar10 closed this as completed Apr 10, 2017
@marczi
Copy link

marczi commented Apr 14, 2017

I think with this modification in version 2.22 the insertBefore parameter for addChildren is buggy. Let's say I have 3 elements and I'd like to add a node before the 3rd one. Now the node is added to the correct position in the "children" list, but it is always renderer as it were added after the last node. It is working correctly in version 2.21

@aleventhal
Copy link
Contributor Author

I put up a PR to fix this but keep the optimization.

@mar10 mar10 closed this as completed Apr 21, 2017
@marczi
Copy link

marczi commented Apr 25, 2017

Thanks for your fix, but unfortunately it still seems to be a little buggy (now it is better than it used to be). The most simple use case is to create a tree with a single node and try to add a second node before this first node. In version 2.21 the newly added node appears correctly before the original single node, but in 2.22.1 it appears after that. The only workaround is to call render(true) on the root node.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants