Non-ReadOnlyBase in ReadOnlyListBase - Any drawbacks?

Non-ReadOnlyBase in ReadOnlyListBase - Any drawbacks?

Old forum URL: forums.lhotka.net/forums/t/4200.aspx


wleary posted on Monday, January 21, 2008

We are working on an client (WPF, WCF) app that contains a significant amount of data, and performance is of utmost concern. In an effort to optimize the performance of some screens, I decided to experiment with flattening the object model a bit, and designed some classes where an outermost ReadOnlyListBase is the only CSLA-derived entity in the hierarchy. I did this for the benefits of having it consistent with all of our other objects, in terms of instantiation and keeping the configuration for it the same.

But, I wanted to know if there was any significant reason why this would be a bad idea. I scanned the forums and the books, and didn't see anything clearly that would suggest it is not good.

Here is an example of the design:

DailyScheduleList : ReadOnlyListBase(DailyScheduleList, DailyScheduleInfo)

DailyScheduleInfo : List<SlotInfo>

SlotInfo : List<SliceInfo>

SliceInfo

The data for the app is retrieved by a date range, typically a month, and the data may vary significantly by day, and each day contains "lists" of slots which each contain lists of slices. It does appear that this method is faster than the way I initially designed it, which was having the lists contain an object that derived from ReadOnlyBase which in turn had a property of type ReadOnlyListBase to contain the next list, though I haven't benchmarked it.

Any thoughts would be appreciated.

ajj3085 replied on Monday, January 21, 2008

Well one drawback is you lose per-property access checks, but that may not be important.

I would suggest though that you're falling into a common anti-pattern; premature optimization, which some call the "root of all evil."

I'd tend to agree.  Build out your application following normal design methodigy (which in your case is Csla based), then go back and rework only if needed.  The fact that you have concerns yet haven't benchmarked is pretty alarming.

wleary replied on Monday, January 21, 2008

Thank you for the feedback. In this case, you are correct that per-propety access checks are unnecessary.

I went ahead and decided to write a test to see if I was indeed making "evil" optimizations. I wrote a test using pure-CSLA and my bastardized CSLA optimization. I already described the bastardized classes, so here are the pure-CSLA:

CslaDailyScheduleList : ReadOnlyListBase<CslaDailyScheduleList, CslaDailyScheduleInfo>

CslaDailyScheduleInfo : ReadOnlyBase<CslaDailyScheduleInfo>

   (This class has a Slots property of the following type)

CslaSlotList : ReadOnlyListBase<CslaSlotList, CslaSlotInfo>

CslaSlotInfo : ReadOnlyBase<CslaSlotInfo>

   (This class has a Slices property of the following type)

CslaSliceList : ReadOnlyListBase<CslaSliceList, CslaSliceInfo>

CslaSliceInfo : ReadOnlyBase<CslaSliceInfo>

To mimic our general data in this particular view, which is a calendar view, I wrote a loop that loaded both sets identically, with 31 objects in the outer list, and each one of those having 10 slots, with 3 slices.

I ran tests using both a local and remote DataPortal. What I found seems to indicate that in this case my optimization were indeed about 10X faster. The test itself is below.

Local data portal:

   Pure/Bastardized: 130 ms/14 ms

Remote data portal:

   Pure/Bastardized:   4972 ms/594 ms

So, clearly, there is a dramatic speed differential. Our app is dealing with inventory and schedules, and many screens are dealing with groups of read-only data by day (that itself may have nested collections), so I'm leaning towards starting to use this more across the board. If anyone else has any thoughts on this, I'd love to hear how you handled situations where you had large quantities of read-only data and wanted to optimize performance in a CSLA-centric design. I love CSLA, don't get me wrong. We are using it throughout the app. But one screen in particular was quite sluggish, and this change sped it up noticeably.

[TestMethod]

public void CompareCslaVsNonCslaCollections()

{

GC.Collect();

GC.WaitForPendingFinalizers();

GC.Collect();

int start;

int stop;

start = Environment.TickCount;

CslaDailyList list = CslaDailyList.GetList();

stop = Environment.TickCount;

Trace.WriteLine("Elapsed time with CSLA: " + (stop - start).ToString());

GC.Collect();

GC.WaitForPendingFinalizers();

GC.Collect();

start = Environment.TickCount;

NonCslaDailyList nonlist = NonCslaDailyList.GetList();

stop = Environment.TickCount;

Trace.WriteLine("Elapsed time with non-CSLA: " + (stop - start).ToString());

}

ajj3085 replied on Monday, January 21, 2008

Well, the key is that your screen was too sluggish. 

Now, regarding your timings; they seem odd to me.  I don't know why Csla would be eating up more resources.  Reflection should be out of the picture after the dataportal call.. what do your internal factory methods look like?

ReadOnlyListBase isn't adding a whole lot of anything; some event handlers and databinding support, but that shouldn't be firing (and if it is, no one should be listening at this point yet).

Something certainly seems fishy.

wleary replied on Monday, January 21, 2008

The internal factory methods are vanilla. No DB access at all, and the only code for loading the objects is in the DP_Fetch of the outermost list. Here they are:

Csla: (The EnableLoading method is a method I added to set IsReadOnly and RaiseListChangedEvents accordingly...since I was loading external to the entity).

private void DataPortal_Fetch()

{

this.IsReadOnly = false;

this.RaiseListChangedEvents = false;

DateTime tmpDate = DateTime.Today;

for (int i = 1; i <= 31; i++)

{

CslaDailyInfo dailyInfo = CslaDailyInfo.Get();

dailyInfo.Date = tmpDate;

dailyInfo.Id = i;

dailyInfo.Slots.EnableLoading(true);

this.Add(dailyInfo);

// Add 10 slots a day.

for (int j = 1; j <= 10; j++)

{

CslaSlotInfo slot = CslaSlotInfo.Get();

slot.ScheduleId = i;

slot.Ordinal = j;

slot.Slices.EnableLoading(true);

dailyInfo.Slots.Add(slot);

// Add 3 slices a day.

for (int k = 0; k < 3; k++)

{

CslaSliceInfo slice = CslaSliceInfo.Get();

slice.Id = k;

slice.AdvertiserId = 100 + k;

slice.AdvertiserName = "Advertiser " + k.ToString();

slice.BookingId = k;

slice.BookingName = "Booking " + k.ToString();

slot.Slices.Add(slice);

}

slot.Slices.EnableLoading(false);

}

dailyInfo.Slots.EnableLoading(false);

tmpDate = tmpDate.AddDays(1);

}

this.IsReadOnly = true;

this.RaiseListChangedEvents = true;

}

Non-CSLA:

private void DataPortal_Fetch()

{

this.IsReadOnly = false;

this.RaiseListChangedEvents = false;

DateTime tmpDate = DateTime.Today;

for (int i = 1; i <= 31; i++)

{

NonCslaDailyInfo dailyInfo = new NonCslaDailyInfo();

dailyInfo.Date = tmpDate;

dailyInfo.Id = i;

this.Add(dailyInfo);

// Add 10 slots a day.

for (int j = 1; j <= 10; j++)

{

SlotInfo slot = new SlotInfo();

slot.ScheduleId = i;

slot.Ordinal = j;

dailyInfo.Add(slot);

// Add 3 slices a day.

for (int k = 0; k < 3; k++)

{

SliceInfo slice = new SliceInfo();

slice.Id = k;

slice.AdvertiserId = 100 + k;

slice.AdvertiserName = "Advertiser " + k.ToString();

slice.BookingId = k;

slice.BookingName = "Booking " + k.ToString();

slot.Add(slice);

}

}

tmpDate = tmpDate.AddDays(1);

}

this.IsReadOnly = true;

this.RaiseListChangedEvents = true;

}

ajj3085 replied on Monday, January 21, 2008

Hmm... it looks like you're doing one db call for each load.  Perhaps you could optimize by getting sets of data at a time?  Search for DeepData example on this site; it shows how to load children and grandchildren in one database call.  That will probably help more than moving away from Csla design.

wleary replied on Monday, January 21, 2008

I am quite familiar with loading all objects in a CSLA hierarchy with one call. We do that exclusively. As I said, this call used for the test does not hit the database. In this project we have objects that contain up to 5 levels deep of nesting that are populated via a single call to the DB, and the resultsets from the data reader is passed to the objects that need it.

My suspicion is that the (in this case, unnecessary) overhead comes from all of the additional work CSLA does when it is dealing with its classes. I see code being called in the ReadOnlyBase  constructors, and assume more work is also being done when serialization occurs. Bypassing this by using lightweight DTO objects wrapped in an outer CSLA entity seems to be significantly faster for this case.

ajj3085 replied on Monday, January 21, 2008

Ok... I couldn't tell exactly what was going on from the code posted.

I wonder if this is related to another post where if rules never get added for a type, it keeps checking and trying to add rules..

Well if your solution works for you and you won't need any of the Csla features then tht's the one I'd pick.

wleary replied on Monday, January 21, 2008

Hmmm...I just went looking for that thread but couldn't find it. I did see it though as addressed in the change log of 3.5.

We do not have any rules for any of the types in this read-only hierarcy, so that may indeed be it. Thanks again for your feedback.

Copyright (c) Marimer LLC