(Note: this is the 3rd of a 4 part post).
To understand where the rest of the 1150 seconds of performance improvement came from, let’s first go back to the concept of n-tier design and understand further layering in the data tier.
Remember that our high level algorithm includes a line that looks like this:
GroupInfo[] agi = DataLayer.GetCellGroupMembers( cell );
What is a GroupInfo? This is a class that I define in my data layer to isolate other tiers from the specifics of how data is stored in AD. The definition of GroupInfo contains an S.DS DirectoryEntry object:
class GroupInfo { private DirectoryEntry de; ... public GroupInfo(DirectoryEntry de) { this.de = de; } public string DisplayName { get { return de.Properties["displayName"].Value as string; } } ... }
DirectoryEntry is an S.DS object that represents an object (in our case ) in Active Directory. GroupInfo does not expose this object. It contains public properties (e.g. DisplayName) that return desired data without exposing how the data are stored.
This layer of abstraction incurs performance overhead. When we’re retrieving data to display, we create 20,000 objects instead of just 10,000. The search code looks somewhat like this:
DirectorySearch ds = new DirectorySearcher(...); ... set up ds ... SearchResultCollection src = ds.FindAll(); GroupInfo[] agi = new GroupInfo[ds.Count]; int ig = 0; foreach(SearchResult sr in src) { DirectoryEntry de = sr.GetDirectoryEntry(); agi[ig++] = new GroupInfo(de); }
First, ds.FindAll() creates a collection with 10,000 search results. We then create a new array with 10,000 elements and iterate through the search results constructing 10,000 GroupInfo objects. Ouch.
How can we avoid all this object creation? Is there anything we can do to exploit the ListView’s support of virtual mode without polluting the 2-tier design? How about if we delay the creation of GroupInfo objects? Consider a class like this:
class SearchResults { private object[] ao; public SearchResults(SearchResultCollection src) { ao = new object[src.Count]; foreach(SearchResult sr in src) ao[i] = sr.GetDirectoryEntry(); } public GroupInfo this[int i] { get { if( ao[i] is DirectoryEntry) { GroupInfo gi = new GroupInfo(ao[i] as DirectoryEntry); ao[i] = gi; return gi; } else return ao[i] as GroupInfo; } } }
The SearchResults class is a wrapper for the data returned from DirectorySearcher. When we create a SearchResults object, we pass it the SearchResultsCollection we got back from DirectorySearcher. It creates an array of generic objects and stores the incoming DirectoryEntry objects returned from the search. The SearchResults class can be used like an array because it defines an indexer property. When you request element i from the class, it will look at the object array. If the array contains a DirectoryEntry object (as it will, initially) it will construct a GroupInfo object for it and replace the DirectoryEntry with the new GroupInfo object. If the array already a GroupInfo object, it will return that instead.
SearchResults, in essence, delays the construction of GroupInfo objects until they are needed. Once constructed, they are then cached for later use. These details are hidden from the user-interface code. The UI code uses SearchResults exactly as it previously used its GroupInfo array. We are providing performance improvement without violating layer separation. True, we would not necessarily have implemented SearchResults if virtual mode list views were not available but such impedance matching is being performed with proper layering discipline. By matching the UI’s use of virtual mode list views with class that also delays object construction, we are providing better performance but still isolating layers.
I don’t recall how much speed up this code provided, but it was significant. I am not providing full details on the GroupInfo object. The real version is slow to construct for reasons that are beyond the scope of these posts.
In my last post on this series, I’ll talk about the last category of changes that I made to speed up my S.DS code.