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.
Quick update. I'd initially not considered modifying SyncWithCurrencyManager because the documentation mentions it's useful for "deeply nested bands" and I'm only three levels deep.
Made the change anyway, and clicking cells is now much faster. Still a slight delay on the first click, but totally usable again. That just leaves the row coloring as the sticking point.
Hello ,
I am glad to hear that you have abated to find a way to make your application faster.
I am not sure how exactly you are coloring your rows, but if you doesn’t reuse appearances object, this could introduce additional delay. I have tried to build a small sample based on your scenario, with the volume which you are using, please see attached sample.
You will notice that if grid doesn’t reuse appearances, it response is slower.
Please let me know if you have any further questions.
I'm seeing a very similar performance in your sample and my code. There is roughly a one second delay (not timing precisely, just counting it out myself) when expanding a middle row. With 320 entries this comes out to somewhere around 3.125ms per row compared to my average of 3.5ms.
I've gotten the go-ahead to explain the software a little more thoroughly and post the code we use to color the rows, which I hope will shed some light on things.
The underlying algorithm is processing situational awareness data collected at multiple points in large, heterogeneous communications networks. To give a very high level overview, I classify things into three broad categories: Entities, Reporters and Reports.
Each physical thing being reported is an Entity. There is at least one Reporter for each Entity, but there are almost always several (2-4). Reports are what Reporters send to indicate the position of an Entity.
The top band represents Entities, the middle band is Reporters of that Entity and the bottom band is Reports from that Reporter. The output is intended to help detect and correct errors in these networks by finding anomalies. The way these anomalies are indicated to the user is (in this version) by coloring the row based on the type of anomaly detected.
Now, the data. The data I've been referring to is not actual data collected in the field, but data produced by one of our other tools through a stress testing scenario. This scenario represents the upper end of database sizes fairly well, but not the structure. In order to produce large amounts of data in a short time, it has amplified the number of Entities greatly. 5,000 Entities in the field is pretty much unheard of.
Instead, realistic data commonly has 10-30 Entities, 2-4 Reporters per Entity, and several thousand to tens of thousands of Reports from each Reporter. I've been using the stress test scenario as it represents a worst case scenario for my algorithm, as the complexity is based on the number of Entities, not Reports. It's a little difficult to explain why, but the middle band (Reporters) are often merged into a single row.
As an example, the most common case is a twelve second periodic report for each reporter, and tests usually last 8 hours (standard work day) with our software collecting data for the entire duration. At five reports per minute we get 3,600 reports per reporter (which there are 2-4 of), so between 7,200 and 14,400 reports under each row in the middle band. There are cases where reports are as infrequent as once a minute and (much rarer) as rapid as hundreds of times per second, this is just an average, run of the mill case.
If we were to defer processing until the InitializeRow event fires, we're faced with an unacceptable problem with responsiveness. At 3.125ms per report, we're looking at 22-45 seconds before the data is accessible in this common case. For many data sets, this is considerably longer than it took to process the entire collection of databases in the first place.
The following is our method which colors the rows. No exceptions are being thrown, the catch is just there to catch if somebody changes something in the schema without updating the code which interacts with the data.
private void ColorRows(RowsCollection rows) { foreach (var row in rows) { try { uint flagUint = (uint)row.GetCellValue("Flags"); WarningFlag flags = (WarningFlag)flagUint; if (flags != WarningFlag.NoWarning) { if ((flags & redFlag) != WarningFlag.NoWarning) { // Color major problems red row.Appearance = redBack; } else { // And minor ones yellow row.Appearance = yellowBack; } } } catch (InvalidCastException ex) { Debug.Assert(false); Debug.WriteLine("Error coloring rows: {0}", ex.Message); if (ex.InnerException != null) Debug.WriteLine(ex.InnerException.Message); } if (row.ChildBands != null) { foreach (UltraGridChildBand band in row.ChildBands) { ColorRows(band.Rows); } } } }
I briefly attempted using InitializeRow to see if that would be faster, and got the same results. Still a little over 3ms per row.
Also profiled the ColorRows method, and the Hot Path makes it look like it's very computationally expensive to either pull data out of a row or iterate over rows in child bands, potentially both.
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.