AdvancedDataGrid -> performance problems and possible enhancements
We have a very large flex 3 application and are finding that the AdvancedDataGrid doesn't perform well for us. On occasion if you resize a column it can take upwards of 7 seconds (freezing inbetween). I have done some tests and played with the AdvancedDataGrid source to identify some problems. I think I've found some areas that can be improved but I wanted to share my proposed solutions first here to see if anybody had any other ideas or suggestions.
I believe in particular our problems are due to a few things:
1. Grouped Columns - A large number of grouped columns with horizontal scrolling enabled - the grid renders all offscreen columns when there's groups.
2. No Horizontal item renderer reuse - When scrolling horizontally, itemRenderers are not reused - even if columns are using the same itemRenderer factory
3. VariableRowHeight - setting variableRowHeight to true on the grid (understandably) causes each itemRenderer to be rendered for all columns and the row height measured
4. Accessing data too frequently - this one is specific to us as we're using inefficient relational data with dot notated resolving - ie <mx:AdvancedDataGridColumn dataField="building.floor.desk.name" /> which resolves on the fly - each column (visible or not) gets rendered and .data= is called at least twice (for the first row double that for measurement)
I've gone into more detail on my findings below:
1. Grouped columns
We have a paged grid with 42 columns in total with about 7 columns per group and scrolling enabled. The problem is while the grid is designed to reuse item renderers when scrolling vertically - when scrolling horizontally as there are column groups every column is pre-rendered.
I've delved into the source code and have found the following:
- in AdvancedDataGrid.getOptimumColumns() there's an if statement:
if (rendererProviders.length > 0 && horizontalScrollPolicy != ScrollPolicy.OFF || columnGrouping)
return displayableColumns;
changing this to remove the "columnGrouping" check does prevent all the offscreen columns from being rendered but then they're not drawn when scrolled - I believe I'd be able to fix this with a little digging
2. No Horizontal item renderer reuse
Assuming I've fixed point 1 - then as you scroll horizontally - the columns would need to re-use item renderers to take full benefit - scrolling horizontally is much jerkier than vertical scrolling.
I feel the code to store the free item renderers could be abstracted a little. Currently to extract a free item renderer you must access the dictionary of columns, then itemrenderer factory, then array as follows:
if (freeItemRenderersTable
&& freeItemRenderersTable
&& freeItemRenderersTable
{
renderer = freeItemRenderersTable
}
This code is repeated throughout the AdvancedDataGrid and Base and BaseEx
I've moved this into an AdvancedDataGridItemRendererCache class which can be called like so:
var renderer:IListItemRenderer = freeItemRendererManager.getItemRendererForColumn(c, factory)
This then means I have a single place to choose whether or not to access the column first in an internally stored data structure. Changing this indeed means that itemRenderers are reused horizontally - this seems to work except for in hierarchical collections where the indentation is slightly wrong for centered columns. Again I think I'd be able to fix this with some digging.
3. VariableRowHeight
It is understandable that every column for a row must be measured to correctly determine the height of the row. In general I feel it's sensible that this needs to occur. However, for a situation such as mine where this is impeding the performance of the datagrid - I can easily and efficiently extract the height for each row without having to measure each column (as I as a user of the AdvancedDataGrid know my data structure). Therefore if the rowHeight measuring were abstracted into an interface I could handle this myself outside of the grid logic and efficiently return the value - this would save 42 columns from being unnecessarily set.
something along the lines of:
interface IAdvancedDataGridRowHeightMeasure
public function measureRowHeight(rowData:Object):Number
4. Accessing data too frequently
Again I reiterate that this may be better handled elsewhere - but as the dataFields of our dataProvider is sometimes more than a simple property - repeated access exacerbated the slow performance. Calling the dataField property of a dataProvider multiple times could be made more efficient by caching the value. I could quite easily "flatten" my relational data before it gets to the grid but seeing as this would be an extra step of code - enabling a cache of the dataField - dataProvider values would be fairly straight forward? In addition as the row is redrawn when the collection is updated - the cache could be recreated at that stage.
If anybody has any other suggestions / comments - I'd like to begin making the changes to the AdvancedDataGrid and submitting a patch to the source if anybody thinks the above changes might be useful? Also - if I'm likely to encounter any difficulties in attempting these changes when I could be better improving the grid in another way please let me know.
Also, how different is the grid in flash builder 4 - it's currently not possible for us to upgrade in the short term. I may have time to put the patches into flash builder 4 however.
thanks
Alex
