I'm using an UltraGrid to display a large amount of hierarchical information. When given a very large data set, things slow to a crawl, and it takes seconds for the grid to respond to mouse clicks. This slowness is not in my code, I've got click handlers and they don't get called until after the delay either. Profiling shows 45.15% of execution time being spent in OnCurrentChanged and another 39.07% in OnCurrentItemChanged.
As an example, I've got a set of data that produces the following output. The top and middle bands in this particular data set each have 5,000 total rows (one row in the middle band beneath each row in the top band). The bottom band has about 1,600,000 total rows, about 300 beneath each entry in the middle band. The delay in a mouse click and my click handler getting called is consistently two seconds with this data set.
Am I simply asking too much of the UltraGrid? The output is produced by an algorithm that can process all of the data in about two minutes, but a post processing step that walks over each of the rows in the bottom band, checking a value and applying a background color based on that value is so slow as to be unusable. In my testing, it took an average of 3.5 ms per row, and at 1.6M rows, that's about an hour and a half just to iterate over the rows of the bottom band. I have not profiled what's taking so long in this loop.
I've gotten around the problem for the next release cycle and eliminated the post processing step entirely, but we're only a few weeks away from the current release and my changes have been deemed too risky to be introduced this late in development, so if there's a solution to this slowness, I still need to implement that one too.
If UltraGrid just can't handle data sets this large, I can simply disable this post processing on large data sets, but I'd really prefer not to. Slimming down the data sets is also not an option (the information in the bottom band is critical), and our users occasionally have data sets even larger than the one I've been testing with.
Hello,
Could you please let me know the exactly version of Infragistics which you are using, also does your sample is consistent with UltraGrid Performance guide http://help.infragistics.com/Help/NetAdvantage/WinForms/2012.1/CLR2.0/html/WinGrid_Formatting_and_Appearance_based_Performance_Improvement.html http://help.infragistics.com/Help/NetAdvantage/WinForms/2012.1/CLR2.0/html/WinGrid_Memory_Usage.html
Could you please post your test application, in order to be able to investigate this further for you.
I am waiting for your feedback.
We're using Infragistics 2010 Vol. 2. Memory usage is fine, using only about 900MB total to process close to 2GB of data and display the results.
And yes, I call BeginUpdate before the function which iterates over the rows, and an EndUpdate afterward. I also do SuspendLayout, SuspendRowSynchronization and SuspendSummaryUpdates and their corresponding Resumes. I'm not sure if any of that is necessary or even detrimental.
Unfortunately, I cannot post the code. This is software for the DoD, and I'm sure I'd be fired immediately for posting the code here, or anywhere for that matter. The dataset contains information classified as "for official use only" and can't be posted either.
I can, however, tell you the general sequence of events.
1. The display collects a number of data sources to be processed and hands them over to a processing engine, which analyzes them asynchronously. At this time, the schema for the UltraGrid is initialized, but no data is populated yet. This is also where we set up our event handlers, styles, etc. and call the SuspendX methods.
2. The engine notifies the display when processing is complete and provides a DataSet with the results.
3. The display calls BeginUpdate, then sets the Grid's DataSource to the DataSet we just got back, and sets a sort order on one of the columns in the top band.
4. The display invokes the method which colors the rows as necessary. This method is blocking.
5. After the row coloring method returns, we call EndUpdate and the various ResumeX methods.
Also, do you have any information on the extremely large delay between clicking on the Grid and the click being registered? I did not mention before, but this delay only occurs when clicking on a different row. Clicking on the same row incurs no delay, and the event handlers fire instantly.
Hi Steve,
A DrawFilter affects the drawing level of the grid. So there are no properties set on the grid itself, it just traps when the cell is about to paint on the screen and says - use this color instead of the one you were going to use. it's a lot more efficient since it only affects the cells that are actually visible on-screen.
Hi Mike,
This is amazing! This DrawFilter works seemlessly (I never used this before and honestly still don't understand how it works but will study and find out because it may help us in other area - we have a very old code base (since 2010) written by many people and UI is extremely slow overall). But this particular Grid painting time is now down to 1 minute, same as if I don't have the entire foreach cell loop.
Thank you very much for you code.
-Steve
There is a Format property on the column, so you could use that to control the number of digits. But Format just calls the ToString method on the value of the cell and passes in the format. So the DotNet framework handles this, it's not something we have any control over, and so we can't add support for colors.
BTW... the grid does not "paint every cell no matter what". InitializeRow fires for all of the rows, but the rows are not painting unless they are in view. So that means you could achieve what you want using the column's Format property and color the cell's text using a DrawFilter and that would be a lot more efficient.
This would be a pretty simple DrawFilter. Something like this:
public class MyDrawFilter : IUIElementDrawFilter { public bool DrawElement(DrawPhase drawPhase, ref UIElementDrawParams drawParams) { if (drawParams.Element is EditorWithTextDisplayTextUIElement) { var cell = drawParams.Element.GetContext(typeof(UltraGridCell)) as UltraGridCell; if (null != cell) { if (cell.Value is int && (int)cell.Value < 0) { drawParams.AppearanceData.ForeColor = System.Drawing.Color.Red; } } } return false; } public DrawPhase GetPhasesToFilter(ref UIElementDrawParams drawParams) { if (drawParams.Element is EditorWithTextDisplayTextUIElement) return DrawPhase.BeforeDrawForeground; return DrawPhase.None; } }
return false; }
public DrawPhase GetPhasesToFilter(ref UIElementDrawParams drawParams) { if (drawParams.Element is EditorWithTextDisplayTextUIElement) return DrawPhase.BeforeDrawForeground;
return DrawPhase.None; } }
You hook it up to the grid like this:
this.ultraGrid1.DrawFilter = new MyDrawFilter();
I am actually troubled by this whole foreach cell loop code too. It should never be written under any circumstance.
Since Infragistics code has to paint every cell no matter what, why cannot you just give us a column format property and we set it to something like
GridColumnFormat = "###,##0;[Red]-###,##0";
and let the Infragistics code paint cell properly?
As for your suggestion of using a common Appearance object instead of accessing each appearance object per cell, it did improve a little bit on runtime: with a 2 level (yes a 3-level is not workable as it is toooo slow so I forced a requirement change to a 2 level) hierarchical grid (first band 7k rows and second 10k), UI rendering time dropped from 3.7 minutes to 3.6 minutes. Not exactly exciting. In comparison, if I remove the loop code for this red cell handling altogether, the painting time dropped to 1 minute (still kinda slow IMO).
I hope you have a better solution for this "Negative cell needs to show Red" problem.
Thanks
Steve
Just at a glance, I can see that this code is very inefficient. You are accessing every single cell in the grid and accessing its Appearance object, which means that for every cell in the 60K rows you have, you are creating a new Appearance object.
Why are you applying the exact same appearance and formatting to each individual cell? You could apply this formatting to the columns or even the Override level and get the same effect by setting a single property instead of 60,000.
the only thing you could not do that for is the color, since that's based on the cell value. But even this could be made a lot more efficient by creating a single Appearance object and assigning it, instead of a new one for every red cell.
Check out the WinGrid Performance Guide for tips on using the grid more efficiently.