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.
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); } } } }
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.
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.
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.
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.