In the past few weeks, we have been evaluating some exciting opportunities to improve performance in Ext JS 4.1. While these optimizations build upon the foundation laid by Ext JS 4.0, they may potentially impact existing code in a couple narrow use cases. So before getting too far down the path, we want to first discuss these ideas and solicit feedback from the community.
Improving performance was one of the major design goals for Ext JS 4.0. Given that the single, most dominant factor in overall performance was layout performance, it is not surprising that layout optimization was a large part of the Ext JS 4.0 release. Performance, however, was not the only goal. Increased flexibility was another major design goal for Ext JS 4.0. To provide this flexibility, many aspects of the user interface were refactored into Components, Containers and Layouts, whereas previously these were often hard-coded aspects of a Component (e.g., tools in a panel header).
When compared one-to-one, the individual layout managers in Ext JS 4.0 handily out perform their counterparts in prior versions. In previous versions, layout managers interrogated, moved and resized DOM elements sequentially. In Ext JS 4.0, on the other hand, the layout managers read the DOM, make calculations and then move/size elements all in separate phases (in parallel). This avoids expensive read/write interleaving and radically improves performance. The layout managers in Ext JS 4.0 also do a much better job of handling auto-sized components such as flowing text or images, or containers of such things.
Unfortunately, all this flexibility has a cost and the sum of these costs can add up quickly. While deeply nested component trees are one, rather obvious, way to see the total cost increase, sometimes cost can come from unexpected places like the combination of configuration options. The perceived impact of all this depends, of course, on the browser. Browsers such as Chrome (WebKit, Safari), Firefox and Opera tend to handle the increased load well. The same cannot be said for IE, especially the older versions like IE8, IE7 and our most beloved IE6.
All this has been widely observed as the community has dug into Ext JS 4.0 and started using it in real-world applications. It is fair to say that the most common issue raised by the community has been poor overall performance and, in particular, poor performance on IE.
It will come as no shock that the dominant element in overall performance remains layout performance. The other contributors are less obvious. Using the Themes example, the breakdown is roughly this for IE8:
- layout: 68% (58% + getStyle:10%)
- render: 23% (14% + renderSelectors:9%)
- misc: 9%
Our first phase of optimization was delivered in Ext JS 4.0.5 where we addressed the contribution of renderSelectors. Even though they were a small part of the overall performance picture, radically improving them was relatively straightforward. In Ext JS 4.0, after elements are rendered to the DOM, renderSelectors are used to find important child elements and store references to them on the component for later use. For example, the renderSelectors for a Button prior to Ext JS 4.0.5 looked like this:
After applying the renderSelectors, the Button component gained a "btnEl" property that referenced the button or anchor Element. This mechanism, while elegant and flexible, did not perform very well on IE as can be seen by its prominent presence in the breakdown above. The solution was to add a new mechanism based on id's. So, in Ext JS 4.0.5, the button element is now found by the id "compId-btnEl" where "compId" is the id of the component.
btnEl : this.href ? 'a' : 'button',
When we applied this to most uses of renderSelectors throughout the framework, it resulted in an 8x improvement in IE8 and, surprisingly, a 25x improvement in Firefox! This effectively pushed renderSelectors off the performance hit list: what was previously 9% of overall performance has effectively disappeared from view.
While the net gain to overall performance was not all that noticeable, this also had the positive side-effect of making id's more readable and useful to tools like Selenium. Unfortunately it had the downside of breaking code that customized renderTpl's. While the fix was simple: add the necessary id attributes to the custom renderTpl; we want to apologize to those adversely effected for not providing an appropriate advisory regarding this change.
The larger optimizations to layout and rendering (discussed below) require internal changes across the framework. As such, the Themes example, which contains one of just about every component and layout, is an unsuitable benchmark at this early stage. As a proof-of-concept, we have devised a simpler, yet we believe representative, test case that required us to change far fewer components and layouts.
The initial test case is a Viewport with a vbox layout of 10 panels each with an hbox layout and 5 child panels. This makes for a total of 60 panels, 50 of which with titles. These 50 panels each use dock layout to dock their headers and those headers use an hbox to contain the text component with the panel's title. The text component's width is flex:1 and its height is auto. The baseline performance for this test, prior to any further optimizations, was the following:
- layout: 1770ms
- getStyle: 255ms (5461 calls)
- render: 400ms (varies widely from run-to-run and ranges between 350ms and 700+ms)
- Total time: 2630ms
(All measurements were made using IE8 on a Windows 7 laptop with Core i3 @ 2.1GHz)
One of the big costs in a layout is the proper handling of auto-size components: width, height or both. In Ext JS 4.0, this often requires a layout to invoke its child layouts multiple times to achieve the correct result. For example, the title in a Panel has an auto height but that is dependent on the element having the proper width as calculated by the layout of its owner (the header) which in turn depends on the width of its owner (the panel). The panel, however, needs the height of the header in order to dock toolbars and the like, but the height of the header depends on the height of the text component it contains.
To further complicate matters, in Ext JS 4.0, changes to a component now automatically trigger the necessary layouts, thereby freeing the developer from having to know which component(s) need to have their layout recalculated. This means that layouts have to be able to run from the bottom-up (e.g., to handle setTitle on a Panel). Layouts in Ext JS 3.x and before were much simpler and were only ever executed top-down. All this complexity was at the root of many of the bugs fixed in Ext JS 4.0.x. This was often both a correctness issue (layouts not being recalculated when needed) and a performance issue (layouts being redundantly recalculated).
Merely making the layouts correct and predictable is not enough. To take the layout optimizations made in Ext JS 4.0 to the next level, we need to minimize read/write interleaving globally not just locally, that is, for the whole component tree rather than for each component. To do this we need a "layout solver": a class that can see the whole problem, organize the various layout managers, track their dependencies, cache their DOM reads, buffer any calculated results and coordinate writing updates to the DOM.
This would also definitively solve one of the biggest complexities in the Ext JS 4 layout system: ensuring that all necessary layouts are recalculated while simultaneously avoiding redundant calculations.
The initial results here are very promising: a 5x layout speed improvement! That is significant enough to drop layout time below the render time! We also see a significant reduction in the number of DOM reads (getStyle calls).
- layout: 290ms
- getStyle: 185ms (3544 calls, down by 1917)
- render: 550ms* (see above)
- Total time: 1370ms (a 2x overall improvement)
With layouts no longer the primary bottleneck in the initial presentation of a page, our attention turned to the new top culprit: rendering. Ext JS 3.x and 4.0 take a hybrid approach to creating DOM elements. The primary element for a component, as well as other elements, are ultimately created using the createElement DOM API. In Ext JS 4.0, the internal structure of a component is produced using the XTemplate class and an internal property called the "renderTpl". This part of the rendering eventually results in setting innerHTML to a block of generated markup.
Generally speaking, innerHTML is a significantly faster approach for generating large trees of elements, especially on IE. This was less of a problem in Ext JS 3.x because many parts of the UI were just markup and not first-class components. This meant much more of the DOM was created via innerHTML. In Ext JS 4.0, now that almost everything you can see is a component, this balance has greatly shifted towards the use of createElement.
The strategy for optimal rendering performance then is to render the entire component tree into markup in one pass and insert that into the DOM with one write to innerHTML.
In our initial tests, this approach further reduces total time by 20%-30%:
- layout: 230ms
- getStyle: 100ms (3222 calls, down by another 322)
- render: 180ms (varies between 150ms and 300ms)
- Total time: 950ms (a 2.5x to 3x overall improvement)
After all that back-story, even the most patient reader is probably thinking:
This all sounds wonderful... and we are all certainly glad that the development team hasn't been ignoring our cries, but what does it mean to me and my code? Beyond the whole 'faster is better' thing...
We are looking at two important areas where these changes will impact existing code...
Impact on Custom Layouts
Significant refactoring will be necessary for a layout class to participate in the new scheme. We don't imagine that too many folks in the community are writing custom layouts, but those that have will have some work to do. The amount of refactoring necessary will depend on how closely the layout follows the read/calculate/write division used throughout Ext JS 4.
For example, the box layout (the base for hbox and vbox) required several "spot" changes, but remained structurally very much the same. The same was true of the dock layout. These layouts are two of the most complex layouts, so we are optimistic that the changes will not be overly drastic in most cases.
Impact on Custom Components
More importantly is how bulk rendering will effect custom components and at the center of this is the onRender method. Currently, onRender is overridden for two very different purposes:
The first use case is unaffected by this refactor, though overriding initEvents (now supported by all components) may often be a better choice. The second use case, however, will not work with bulk rendering. Instead, such code will need to move to the new beforeRender method.
- To do work that requires the component's elements to exist (like adding listeners), after first calling the base class.
- To make last minute modifications that effect the rendering, before calling the base class.
Detailed information will follow at a later date. We are only now in the early stages of this effort. Rather than wait for all the details to be sorted out, implemented and documented, we wanted to get feedback on the general approach.
What are your concerns, questions, suggestions?