ExtJS - Performance Tuning

Main page
https://vimeo.com/37636229
https://krraghavan.wordpress.com/2014/05/06/extjs-grid-checkbox-selection-model-performance-improvements/
http://www.sencha.com/blog/ext-js-4-1-performance - reading (scroll to "Use Page Analyzer to measure performance" section)
http://www.sencha.com/blog/optimizing-ext-js-4-1-based-applications - done reading
http://www.sencha.com/forum/showthread.php?153565-ExtJS-Performance-Best-Practices/page1 - done reading
http://www.html5rocks.com/en/tutorials/speed/v8/

What are common causes for performance problems?

  • Network latency which affects initial startup time heavily
  • Store load time. This refers to the amount of time it takes to parse the JSON. This depends on how much data you have to work with.
  • CSS processing
  • JavaScript execution
  • DOM manipulation

What are the things that you should do to improve performance?

  • Minimize network latency
  • Check your event listeners.
  • Use the afterrender and onrender event only when necessary
  • Remove doLayout and doComponentLayout calls
  • Reduce container nesting
  • Use Containers instead of Panels when possible
  • Reduce border layout nesting
  • Use Ext.suspendLayouts and Ext.resumeLayouts when adding or removing a lot of components or elements
  • Use Page Analyzer
  • Use deferred rendering
  • DOM is the slowest thing ever. Destroy all items that aren't visible. Reduce the number of components, panels, containers, etc…
  • Watch out for leakage
  • Disable animation and visual effects on old / slow browsers.
  • Batching

How to minimize network latency?

  • Parallel your downloads. Load your CSS files using one sub-domain. Load your images using another sub-domain. Load your JS files from another sub-domain, etc.
  • Concatenate and compress your resources.

To minimize application startup time, you need to bear in mind that browsers impose a limit on the number of concurrent network connections to any one domain. This means that if many files are requested from one domain, once this quota is used, subsequent downloads will be queued, and they will be processed only when a connection slot becomes free. Newer browsers have higher limits, but this makes optimization all the more important in older, slower browsers.

Use Sencha SDK tools to build a single, concatenated JavaScript file containing all the required JavaScript used by the application. The SDK “create” command analyzes the application by loading the page, loading all files referenced by all “requires” and “uses” properties of class definitions. It then creates a single JavaScript file containing all required class definitions in the correct order.

How to change your CSS selectors to improve performance?

CSS selectors are matched right to left, following the DOM’s parentNode pointer. This means that a selector like:

.HeaderContainer .nav span

will be processed, and an attempt made to match it on every span in the document. The parentNode axis will be followed to try to find ancestor nodes with the two specified classes. It’s much more efficient to use a single, identifying class name on elements to be styled or use IDs if possible. The point is that we should try to simplify the selector expression as much as possible so that the browser does not have to examine the parent hierarchy.

How can I optimize my JavaScript code to improve performance?

  • Optimize code which is repeated frequently. It is your application, you should have a gut feeling of which portions of your code are frequently executed. You can also use the "Profiler" features that comes with IE Developer Tools, or Firebug, etc to determine which portion of your code are most frequently executed.
  • Optimize code which is executed at render or layout time.
  • Try not to execute any extra code at initial render or layout time.
  • Move invariant expressions outside of loops. (invariant means constant). If an operation always produce an result that is a constant. See if you can move that operation to outside the loop.
  • Use for (…) rather than Ext.Array.each. Ext.each invokes the passed function for every record in the array.
  • If a function performs its task conditionally and is frequently called, check the condition outside the call, and only call it if necessary.
  • Function calls have a "setup and teardown" price. Setup and teardown of a call frame (apparatus needed to make a function call) is slow on bad JavaScript engines. Just like in C, for every function calls, the language / computer has to push all the variables into a stacks or registers. Try to minimize function calls.

Check your event listeners:

The way your application uses event listeners is a key performance concern. For example, you might set a load event to fire the first time a store gets data. If you’re not careful, that same load event might fire every time the store gets new data. Turning that off, and having load fire only the first time the store retrieves data can make a substantial difference in the overall performance of your application. To do this, add single:true to your listener:

listeners: {
    load: onFirstLoadData,
    single: true
}

Why should we be careful with the afterrender event?

The afterrender event handler is invoked after all DOM elements already exist. Changing elements after rendering causes reflows, slowing your application. Instead, adjust classes and set styles before rendering takes place using beforerender so that the element renders with the correct classes and styles. Some code that must run after render may require the element size to have been acquired. Ext JS 4.1 provides a new event, boxready, which you should consider using if that is the case. It runs after the size of a component has been determined.

One of the biggest, yet distributed across the framework, costs we found in profiling 4.1 was the cost of DOM updates made in methods like afterRender or onRender. Many components made "harmless" calls like createChild('div') or addCls or removeCls in these methods. We made a big push to move as much of this stuff as possible into beforeRender and the renderTpl which eliminated dozens of reflows. The same should be considered for custom components, especially if those components are heavily used. I'm not sure how helpful this will be in general, but in 4.1 we added Ext.getDetachedBody() as an alternative to Ext.getBody(). This was to optimize cases where we had to render a component (like a drag-drop proxy) but didn't want to pay for extra reflows. The "detached body" is just a div that is not part of the document, so anything rendered into it cannot be measured, but you can still set classes, styles and event listeners. We simply move these elements to the real getBody on-demand. http://www.sencha.com/forum/showthread.php?153565

Why should we remove doLayout and doComponentLayout calls?

Simply put, remove these expensive calls whenever possible. In older versions of Ext JS (prior to 4.0), doLayout was how you told the framework you were done with a component or container and to go ahead and recalculate its layout. Even in Ext JS 4.0, these calls were sometimes needed after direct DOM updates or to work around certain bugs.

With Ext JS 4.1, layouts are triggered differently, so your code should seldom need to call doLayout or doComponentLayout. If it turns out that these calls are still needed as bug workarounds in your application, please file a bug report so we can fix it.

The only time doLayout or doComponentLayout should be needed for non-bug reasons is when the DOM is directly changed by application code. Since the framework is unaware of such changes, these calls are needed to update the affected layouts.

Why should we reduce container nesting?

We often see applications with excessive nesting of containers. For example, there might be a container that owns a single container which owns multiple components where all the work is taking place. You can often eliminate the outer container and accomplish the same thing with one container. It is important to remember that each container takes time to initialize, render, and layout, so the more you can get rid of unneeded nested containers like this, the faster your application will run.

Why do we need to replace Panels with Containers?

Panels are more powerful (and expensive!) than basic Containers. So specify xtype: 'container' to avoid having your application use the default 'panel'.

{
    xtype: 'container', // defaultType is 'panel'
    items: [ ... ]
}

Reduce border layout nesting:

With Ext JS 4.1, there are many cases where you no longer need to use nesting with the border layout. Removing this nesting will reduce the time to initialize, render, and layout your components. Previous versions of Ext JS required nesting when you needed, for example, to have two or more instances of the same region. You also had to nest border layouts if you needed two North regions above the center region. Now you can just have two North regions as part of the single border layout.

With Ext JS 4.1, regions can be added dynamically when they are needed, instead of having to add all regions up-front and hide them when not in use. You can also use the weight property to give precedence to a region—for example, giving precedence to the West region over the North region. All these changes mean that you should not often need nesting with border layout, speeding up rendering of components that use that layout.

Eliminate extra layouts with Ext.suspendLayouts and Ext.resumeLayouts:

Ext JS 4.1 provides two new methods, Ext.suspendLayouts and Ext.resumeLayouts, to help you coordinate updates to multiple components and containers. Adding two items in rapid succession to two containers, for example, causes multiple layout and render operations to be performed. If you use Ext.suspendLayouts before you add these items the framework will no longer perform the layout for each individual item. Once you’re done making your additions, use Ext.resumeLayouts and the framework will perform a single render and layout operation.

{
    Ext.suspendLayouts();
    // batch of updates
    Ext.resumeLayouts(true);
}

Use Ext.suspendLayouts and Ext.resumeLayouts whenever doing multiselect operations on the grid or inserting/deleting multiple records. Internally the grid implementations loop through each record and then fire the appropriate add/select/deselect/remove events which in turn updates the layouts (to (say) update a checkbox to indicate selection)

Use Page Analyzer:

See Page Analyzer

Tune your grids:

To display large data set inside a grid, we need to use infinite scrolling. Infinite scrolling relies on a page cache where a paging scroller object stores pages of the dataset before the user scrolls to the part of the grid that needs to display it. As the user scrolls, the cached data becomes visible and then disappears off the top of the page and is typically no longer kept in the DOM. The main way to tune this is to keep the DOM size as small as possible, and cache data on the client side to minimize server round-trips.

When a data store is configured with buffered: true, a PagingScroller object is instantiated to monitor scrolling of the View (grids are specially configured data Views), and attempt to maintain a cache of pages ready to provide data immediately it becomes needed as the view traverses down the dataset.

The PagingScroller requests that the data store ensures that the trailingBufferZone, and the leadingBufferZone are in the cache. This causes the store to calculate which pages that requires, and ensure they are cached. The store only makes Ajax requests pages that are not already in the cache.

You need to maintain a balance between how much data is visible and how frequently re-rendering is required. If your application targets a fast browser, you can display a larger set of rows and data. For slower browsers, display a smaller table with fewer rows that reaches a visible edge more often and has to refresh more frequently.

To help you tune grids, Ext JS 4.1 includes an example called Infinite Grid Tuner, which you can find in the Examples directory. Infinite Grid Tuner includes a large data set—50K of records—and lets you set different ways to load the data into the store to prime the prefetch buffer. For example, you can simulate Ajax latency, change the number of rows that are prefetched, and tune the table size. You can experiment with the various parameters, shown on the left of the page, to see what works best in the browser(s) your application targets.

/sdk/extjs/examples/grid/infinite-scroll-grid-tuner.html

Using the Infinite Grid Tuner, you can also adjust the purge page count setting of the store. This sets the amount of data that’s removed from the page cache after it’s been rendered. If you set this number to 0, it keeps all of the data in the buffer, which means that if the user scrolls back up the grid, the data is reloaded without having to refetch data from the server.

A grid’s visible data set can be thought of as a sliding window. Similarly, the page cache can also be thought of as a sliding window into all the data associated with the grid. You can change the size of both using the tuner. You can also set the rules for when each is replenished, determining at which point in user scrolling (the number of rows from the visible edge) to request more data for the visible part of the grid and the page cache.

How to watch out for leakage?

Consider this example:

Ext.define('MyClass', {
    ...

    constructor: function(config) {
        ...

        this.store.on('load', function() {
            ...
        }, this);
    }
});

The thing to realize is that a new listener function is created each time this constructor runs. This not only takes time but could potentially leak loads of memory though a closure. The preferred pattern looks like this:

Ext.define('MyClass', {
    ...
    constructor: function(config) {
        ...
        this.store.on('load', this.onStoreLoad, this);
    },

    onStoreLoad: function() {
        ...
    }
});

In this case the listener function is created once and stored on the class. Each instance will share the same function. The overhead for creating the function is incurred just once and there's no risk of a leaky closure. Even better, this style of coding makes it easy for subclasses to change the listener with a simple override. 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.

Batching:

Changing a field value in a record will immediately update the grid. If you want to update several fields it's more efficient to batch them:

record.beginEdit();
record.set('name', 'Tom');
record.set('age', 17);
record.set('member', true);
record.endEdit();

The more fields you change the more noticeable the effect will be. In 4.x, you can use the multi-value form of set which has a built-in begin/endEdit mechanism:

record.set({
    name: 'Tom',
    age: 17,
    member: true
});

Another example is adding components to a container. The container will recalculate its layout every time add() is called. Passing multiple items to the same add() call will be much more efficient.

// slow
container.add(panel);
container.add(button);
container.add(grid);

// fast
container.add(panel, button, grid);

// using an array, also fast
container.add([panel, button, grid]);

In cases where no other batching mechanism exists it can help to suspend layouts temporarily:

container.suspendLayout = true;
doSomethingThatCausesLotsOfLayoutChanges();
container.suspendLayout = false;
container.doLayout();

New to 4.1, layouts are suspended via method call to avoid problems that we encountered via nested suspend/resume logic (the boolean suspendLayout is still respected). This can be done on a single component:

container.suspendLayouts();   // plural "layouts"
...
container.resumeLayouts(true);  // true=run layout if 0 suspend count

Or if you are manipulating multiple components, there is a global suspend mechanism:

Ext.suspendLayouts();
...
Ext.resumeLayouts(true); // true=run layouts if 0 suspend count
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License