Ext JS has been getting more flexible with each release. You can do many more things with it these days than you used to be able to, but there has been a performance cost associated with that. In many cases this performance degradation is down to the way the framework is being used, as opposed to a fundamental problem with the framework itself.
There’s a whole bunch of things that you can do to dramatically speed up the performance of an app you’re not happy with, and Nige “Animal” White took us through them this morning. Here’s what I was able to write down in time:
Slow things
Nige identified three of the top causes of sluggish apps, which we’ll go through one by one:
- Network latency
- JS execution
- Layout activity
Network latency:
- Bad ux – got to stare at blank screen for a while
- Use Sencha Command to build the app – single file, minimized
- 4810ms vs 352ms = dynamic loading vs built
JavaScript execution:
- Avoid slow JS engines (he says with a wry smile)
- Optimize repeated code – for loops should be tight, cache variables outside
- Ideally, don’t do any processing at render time
- Minimize function calls
- Lazily instantiate items
- Use the PageAnalyzer (in the Ext JS SDK examples folder) to benchmark your applications
- Start Chrome with –enable-benchmarking to get much more accurate timing information out of the browser
Layouts
Suspend store events when adding/removing many records. Otherwise we’re going to get a full Ext JS layout pass for each modification
1
2
3
4
| grid.store.suspendEvents(); //do lots of updating grid.store.resumeEvents(); grid.view.refresh() |
Ditto on trees (they’re the same as grids)
Coalesce multiple layouts. If you’re adding/removing a bunch of Components in a single go, do it like this:
Coalesce multiple layouts. If you’re adding/removing a bunch of Components in a single go, do it like this:
1
2
3
| Ext.suspendLayouts(); //do a bunch of UI updates Ext.resumeLayouts(true); |
Container#add accepts an array of items, which is faster than iterating over that array yourself and calling .add for each one. Avoid layout constraints where possible – in box layouts, align: ‘stretchmax’ is slow because it has to do multiple layout runs. Avoid minHeight, maxHeight, minWidth, maxWidth if possible
At startup:
- Embed initialization data inside the HTML if possible – avoids AJAX requests
- Configure the entire layout in one shot using that data
- Do not make multiple Ajax requests, and build the layout in response
Use the ‘idle’ event
- Similar to the AnimationQueue
- Ext.globalEvents.on(‘idle’, myFunction) – called once a big layout/repaint run has finished
- Using the idle listener sometimes preferable to setTimeout(myFunction, 1), because it’s synchronous in the same repaint cycle. The setTimeout approach means the repaint happens, then your code is called. If your code itself requires a repaint, that means you’ll have 2 repaints in setTimeout vs 1 in on.(‘idle’)
Reduce layout depth
Big problem – overnesting. People very often do this with grids:
1
2
3
4
5
6
7
8
9
10
11
| { xtype: 'tabpanel', items: [ { title: 'Results', items: { xtype: 'grid' } } ] } |
Better:
1
2
3
4
5
6
7
| { xtype: 'tabpanel', items: { title: 'Results', xtype: 'grid' } } |
This is important because redundant components still cost CPU and memory. Everything is a Component now – panel headers, icons, etc etc. Can be constructing more Components than you realize. Much more flexible, but easy to abuse
Lazy Instantiation
New plugin at https://gist.github.com/ExtAnimal/c93148f5194f2a232464
1
2
3
4
5
6
7
8
| { xtype: 'tabpanel', ptype: 'lazyitems', items: { title: 'Results', xtype: 'grid' } } |
Overall impact
On a real life large example contributed by a Sencha customer:
Bad practices: 5187ms (IE8)
Good practices: 1813ms (IE8)
1300ms vs 550ms on Chrome (same example)
Good practices: 1813ms (IE8)
1300ms vs 550ms on Chrome (same example)
Colossal impact on the Ext.suspendLayout example – 4700ms vs 100ms on Chrome
Less Is More
The more panels (and components in general) you create, the more stress it will put on the layout system and the larger the DOM will be.
Handler Functions
Load Times
Disabling Animations
Different Settings For Different
Browsers
Most components clean up everything in a good way, but what i was talking about are the things users create on their own. Let's say you extend panel as a class, use the initComponent method to create children there and stores and save them into references of the class, then you need to remove them.
You can always (for every extension of component) use the onDestroy method.
Here as an example what we do for the ComboBox:
Performance
is a top-tier concern that has been carried over to my Ext JS (3 & 4) work.
- Don't over nest. Most newbies
nest panels several layers deep when a single panel will suffice.
- Don't use a panel when a
container will do. Likewise for other classes.
- Don't gratuitously use a panel
when some simple HTML or an Ext.Template will
suffice.
- Production builds should use
concatenated, minified versions of all the source to improve load time.
- Production builds should
generally use a custom ExtJS build to reduce the file size.
- Be wary of components or stores
that don't get destroyed when they are discarded, memory can quickly leak.
- Create abstracts. This cuts
down on code duplication and bunch of other good things.
- DOM is the slowest thing ever!
There is a trade-off with cute "features" and performance. If
you have a TabPanel or Card layout, use deferred rendering. I go as far as
to destroy cards (or remove all items) that aren't visible.
- Code smart... there are tons of
tricks to accomplish the same thing but with more efficient code.
Less Is More
Most of
the ExtJS applications I've seen suffer from panel explosion. Everything is
done using a panel, or to be more accurate, everything is done using several
panels.
There are alternatives.
The first problem is over-nesting. Panels within panels within panels, when one panel would suffice. Often people don't even realize they're doing it.
The next problem is using a panel when there are much more lightweight alternatives. Everyone loves panels but truth be told they're overkill for most parts of a UI. I once saw a UI that displayed a few dozen thumbnail images and each image was rendered as the HTML content of a different panel. All those panel were totally unnecessary. Consider instead
There are alternatives.
The first problem is over-nesting. Panels within panels within panels, when one panel would suffice. Often people don't even realize they're doing it.
The next problem is using a panel when there are much more lightweight alternatives. Everyone loves panels but truth be told they're overkill for most parts of a UI. I once saw a UI that displayed a few dozen thumbnail images and each image was rendered as the HTML content of a different panel. All those panel were totally unnecessary. Consider instead
1.
Could you use HTML and
CSS instead of a full component? An Ext.Template or Ext.XTemplatemight prove helpful.
2.
Could you just use a
custom Ext.Component? There are various config settings for
injecting HTML into a component: autoEl, html, tpl & data.
3.
Could you use a DataView to render all of your data instead of a
number of individual components?
4.
Could you use an Ext.container.Container rather than an Ext.panel.Panel to reduce the overhead?
The more panels (and components in general) you create, the more stress it will put on the layout system and the larger the DOM will be.
Component Render time Fiddle:
http://jsfiddle.net/prajavk/7bKQn/
Handler Functions
The
functions used for callbacks and listeners are a common source of problems.
They not only take time to create but can also form expensive closures.
Consider this example:
Consider this example:
Ext.define('MyClass', {
...
constructor:
function(config) {
...
this.store.on('load', this.onStoreLoad, this);
},
onStoreLoad:
function() {
...
}
});
For completeness, I
should point out that my use of on is probably misguided here. Though
there are other factors to consider, it would usually be better to write it
using mon instead:
this.mon(this.store, 'load', this.onStoreLoad, this);
This will ensure that
the listener is automatically removed when instances of MyClass are destroyed.
xtypes
A
common myth is that xtypes provide a performance boost through
lazy-instantiation. In general this is not true. As soon as you add an
xtype-config to a container it will instantiate the full component, even if it
isn't visible.
Whilst xtypes have many great uses they usually have no measurable impact upon application performance or resource consumption.
Whilst xtypes have many great uses they usually have no measurable impact upon application performance or resource consumption.
Load Times
To
improve load times in production builds:
1.
Do not use Ext.Loader.
2.
All JS files should be concatenated and minified. Even on a fast
internal network this can make a huge difference.
3.
All CSS files should be concatenated and minified.
4.
Consider building a custom version of ext-all.js. Are you really
using the whole library?
5.
If you use a lot of images then image sprites can help reduce
the number of requests and also provide an implicit form of pre-caching for the
other images in the sprite.
Disabling Animations
It can
help to improve UI snapiness if you disable animations. This is something you
could do selectively such that faster browsers have the animations but slower
browsers don't.
Different Settings For Different
Browsers
Whilst
you may be required to support older versions of IE, it isn't necessarily the
case that you need to provide an identical UI to the one you offer in newer,
faster browsers.
Are there things you could cut in older browsers to help performance? Animations and visual effects are an obvious example but there are others. What about the page size of your grids? It might help to reduce it in older browsers. Many UIs have redundancy built in to make them more convenient, e.g. 3 different ways to do the same thing.
Are there things you could cut in older browsers to help performance? Animations and visual effects are an obvious example but there are others. What about the page size of your grids? It might help to reduce it in older browsers. Many UIs have redundancy built in to make them more convenient, e.g. 3 different ways to do the same thing.
Do
you make a custom build via the "builder" provided in the ExtJs
package?
Yes.
This can help load times but probably won't improve UI snapiness. I wouldn't do
this until your app has matured a bit, working with a cut down build gets
really annoying when you need to use the bits you've cut out.
How
would I make sure they are destroyed?
When a component is created it is registered with the
component manager. The component's destroy()method
will, among many other things, unregister it with the manager. This is
important because the JS garbage collector can't reclaim a component if there's
still a reference to it from the manager.
You can see all the components in your app using Ext.ComponentManager.all.
ExtJS does what it can to destroy components for you. When you destroy a container it will destroy all of its children for you. When you remove a component from a container it will, by default, be destroyed. When a window or tab is closed the default is to destroy it.
You can see all the components in your app using Ext.ComponentManager.all.
ExtJS does what it can to destroy components for you. When you destroy a container it will destroy all of its children for you. When you remove a component from a container it will, by default, be destroyed. When a window or tab is closed the default is to destroy it.
Recommendation:
Do not use iFrames, but go for a single page
app instead.
Regarding the memory leaks: loading a blank page into the iframe will remove the DOM for sure, but not the JS objects / instances you created unless you take care about it. Check the Chrome developer tools to spot out what is leaking.
You can create a custom build of the Ext JS library in case you are using MVC:
http://docs.sencha.com/extjs/4.2.1/#...etting_started
Regarding the memory leaks: loading a blank page into the iframe will remove the DOM for sure, but not the JS objects / instances you created unless you take care about it. Check the Chrome developer tools to spot out what is leaking.
You can create a custom build of the Ext JS library in case you are using MVC:
http://docs.sencha.com/extjs/4.2.1/#...etting_started
Grid
and Stores:
In
case you create a grid using a store and destroy the grid, it does not mean
that the store automatically gets destroyed as well.
This is why I recommended to check for the amount of store instances, because in case you keep them including the data it would explain the leaks.
Take a look at the autoDestroy config for stores:
http://docs.sencha.com/extjs/4.2.1/#...fg-autoDestroy
This is why I recommended to check for the amount of store instances, because in case you keep them including the data it would explain the leaks.
Take a look at the autoDestroy config for stores:
http://docs.sencha.com/extjs/4.2.1/#...fg-autoDestroy
Most components clean up everything in a good way, but what i was talking about are the things users create on their own. Let's say you extend panel as a class, use the initComponent method to create children there and stores and save them into references of the class, then you need to remove them.
You can always (for every extension of component) use the onDestroy method.
Here as an example what we do for the ComboBox:
Code:
onDestroy: function()
{
Ext.destroy(this.listKeyNav);
this.bindStore(null);
this.callParent();
}
I have extended
Ext.data.Strore as follows
Ext.define('My.grid.Store', {
extend: 'Ext.data.Store',
and I implemented
onDestroy: function() {
alert("In store.onDestory() function");
this.callParent();
}
Ext.define('My.grid.Store', {
extend: 'Ext.data.Store',
and I implemented
onDestroy: function() {
alert("In store.onDestory() function");
this.callParent();
}
References:
Comments
Post a Comment