5 May 2012 3:44 PM #1
Configured Heights and Widths Much Faster Than Calculated Heights and Widths
I have been designing a word game application using Ext-JS 4.0.7. It involves a gameboard (Ext.container.Container) with a 15 by 15 grid of squares (Ext.container.Container) using a table layout. Each square may contain a single letter tile (Ext.Component). Everything worked great. Performance was snappy. Then a couple days ago I decided to run my application using 4.1.0. What a big disappointment. It took about 10 secs to load the game when it used to take less than a second.
After reading the Optimizing Ext JS 4.1-based Applications blog post I began to investigate why it runs so slow under 4.1.0. I used the Page-Analyzer (very useful) that comes with 4.1.0 and it helped me identify a few issues. When the application launches, I have to dynamically build the gameboard from the current game state. I iterated over the gameboard state and added letter tiles to the squares that need them. The Page-Analyzer indicated that a layout occurred every time I added a new tile. So I called Ext.suspendLayouts before I started adding tiles and then called Ext.resumeLayouts after adding all the tiles. But then the tiles didn't render. Somewhere I read that the doLayout and doComponentLayout methods were replaced by updateLayout method. So after the Ext.resumeLayouts method call I called updateLayout on the gameboard component (an Ext.container.Container) and the tiles started rendering. Suspending and resuming layouts helped quite a bit with performance. Instead of taking 10 seconds to load the game (about 1 second per tile) it took about 1 second. The Page-Analyzer verified that I no longer had a layout per tile being added. I thought cool but it is still not as snappy as 4.0.7. So I investigated further. I was using CSS to set the height and width of the squares and tiles. The Page-Analyzer was indicating that each squares' and tiles' height and width was being calculated. I then decided to see what would happen if I configured the square component and the tile component with explicit height and width configurations while removing the CSS height and widths. That improved performance 4 times. It was taking about 600 ms to render the gameboard. Now it takes about 150 ms. The Page-Analyzer indicated that each squares' and tiles' height and width was configured (as opposed to calculated).
- Use the Page-Analyzer to help determine where multiple layouts are occurring so you can add Ext.suspendLayouts and Ext.resumeLayouts. (You may still need to call updateLayout on the container you added components to.)
- Using configured component heights and widths may also help improve performance.
I believe the intention was to eliminate the need to all methods like doLayout, doComponentLayout, and updateLayout. Do you know why I still had to call updateLayout after Ext.resumeLayouts? Do you suggest anything else to get the tile components to render?
I couldn't find documentation on the Ext.suspendLayouts, Ext.resumeLayouts, and Ext.container.Container#updateLayout methods in the API documentation. I only know of them because of the blogs posts.
Thanks Ext-JS development team. Keep up the good work.
5 May 2012 8:41 PM #2
- Join Date
- Apr 2007
- Sydney, Australia
- Vote Rating
Configured it fastest
Twitter - @evantrimboli
Don't be afraid of the source code!
6 May 2012 6:09 PM #3
It's great to hear that a little perseverance with Page Analyzer reaped such dividends, I suspect many people would have just given up at the initial disappointment.
Of course that could be quite a large architectural change and the only real benefit is performance. If your performance is acceptable then using a container per square may give a more elegant design. Generally I try to write everything to be lightweight from the outset, it's easier than trying to rewrite half your app when the performance creep catches up with you.
7 May 2012 7:28 AM #4
Yesterday I changed the gameboard to an Ext.Component and used the renderTpl config to create a 15x15 table. Then in the afterRender method I used the Ext.dom.Query to get a reference to each square which I stored off in a 15x15 array for quick access later. (I could have used the renderSelectors config but that would have required setting up an object with 225 properties and selectors.) At first I attempted to attach a click event handler to each square but I had issues with some of the squares which have text in them. Whenever I clicked the text in one of the squares, the target of the click event was the span with the text instead of the div representing the square. (I could have done some work to determine that the text was clicked and then searched for a parent div representing the square.) Then I decided to add a single managed click listener to the gameboard itself with the delegate option set to a selector that selects the square instead of individual click listeners to each square. That works perfectly. The target of the event is always the square div. Once that was done I had to make a few changes to use the methods from Ext.dom.Element since my squares are no longer Ext.container.Containers. (Not as simple as working with components.)
I then tested my changes in the Page-Analyzer. And of course it showed extremely quick layout runs mainly because I eliminated most of them by not making the squares containers.
I then decided to test the effects of Evan's suggestion of setting flush to true when I called Ext.resumeLayouts and not call updateLayout on the main container (gameboard). I used Page-Analyzer once again, but this time it was blazing fast. Before, when I was explicitly calling updateLayout, it was taking about 150 ms to do the layout. Now it takes about 6ms. This is just as fast as not using containers for squares. So I decided to stick with the square containers for now with the only performance drawback happening during startup. It takes about 300 ms total during start up when I use square containers but about 150 ms without square containers. Both are less than a second so it is acceptable and working with containers is easier than trying to work with the DOM.
I looked at the code for resumeLayouts. It appears to be a static method on AbstractComponent and aliased as Ext.resumeLayouts. It also appears to be an instance method on AbstractComponent. Similarly for suspendLayouts. I also looked at the framework's uses of these methods. Most use Ext.suspendLayouts/Ext.resumeLayouts instead of the suspendLayouts/resumeLayouts instance method on a component. Are the instance methods to be considered private? If not, then when should we use the instance method as opposed to Ext.suspendLayouts/resumeLayouts? (FYI, these methods are not documented in the API docs.)
Why isn't the default for flush in the resumeLayouts method true? It seems like this would be the more common case. (Most of the calls to resumeLayouts by the framework pass t When would I not want to force a flush?
When I was reading the documentation for a container's detachOnRemove config, it mentioned Ext.getDetachedBody. There does not appear to be any API documentation on this method. Can we consider this method public?
Thanks again for the Page-Analyzer. It is a useful tool. (However, I don't really understand how to use the Performance tab. It seems to be coupled with the "Update Stats" button.)