Tuesday, June 08, 2010
Evaluating View Model Options
If you are building WPF or Silverlight applications, chances are you are using the MVVM Pattern (see below if you are not familiar with this pattern). So far, so common. Once you look at the details however, there are some interesting aspects to this setup. A recent discussion on my fellow “Advisors” on the Microsoft Prism Advisory Board got me interested in investigating different options to create the “ViewModel” (VM) part of this pattern. After all, there are quite a few different ways to go about this, all with unique (and sometimes only perceived) advantages and disadvantages. I hence set out to create a few test setups of a simple model and view-model combination (a simple Customer example) to test in particular performance and memory consumption. This post presents some of the results of those tests and invites readers to comment on the findings and suggest other options and perhaps test them on their own (my test setup project can be downloaded here).
What is the MVVM Pattern? Well, I am glad you asked! It at heart is pretty simple, actually. If you have any sort of XAML based UI (and really even others as well, although this article does not concern itself with that) and you want to use it to edit data (the typical business application scenario), then you will quickly discover that this data is often not very well suited for binding, since it was not specially designed for UI needs. For instance, you may have a FirstName and a LastName property/field in your data, but you want to bind to the full name. Or you may want to set something visible based on a flag, but the flag is boolean and in WPF/SL you need a property of type Visibility. To make this easier, the idea of a “view model” arose, which is a special version of the “data” (or “model”) that massages the data into a more suitable format (by adding calculated properties, or adding properties with new types, or even consolidating multiple data feeds, and so forth). The result is something that is much easier to use then the raw data. For a more complete discussion of this pattern, check out the explanation in an article on MSDN magazine by Josh Smith, or on the Wikipedia.
As with many performance test examples, it is always difficult to create a setup that provides data that is meaningful in the real world. I thus had to create something that although somewhat contrived, should still provide some meaningful insight. I settled on a simple Customer scenario. My “model” is a hardcoded list of Customer objects. I am not retrieving them from a database, but I am creating them in-memory based on randomly combined names and such. To create a meaningful sample size, I am creating 100,000 Customer objects as my data source. Retrieving 100,000 customer records is not something you would normally do in your interfaces (at least I hope you wouldn’t... users would have to use a very small font to see them all... ;-)), but I wanted to create a large sample size to get results that are a bit easier to distinguish and also to compensate for the relatively few properties I have in my model. (Even a simple “customer + order information and detail” model could have a significant number of properties). Note that I handle all the customers independently. There would be more efficient ways to handle 100,000 customers (and especially related data such as sales regions that are the same for each customer), but the purpose of this simulation is to pretend these are completely independent view models. Otherwise, the resulting performance data would be less meaningful.
Each customer has a first name and last name property, as well as a company name. Furthermore, there is a flag indicating whether the customer has a set credit limit, and if so, the credit limit is stored in another property. Finally, there is a short list of sales regions the customer may be assigned to. This list is an independent data source (although I put it inside each model for simple binding as is the purpose of MVVM). The customer itself is assigned a sales region ID.
In all the different approaches of the view model, I create the same type of behavior and information:
- All properties in the view model need to bind properly.
- A FullName property exposes first name + last name.
- A SalesRegion property exposes the name of the currently selected sales region, which is only identified by ID in the Customer object.
- A HasCreditLimit_Visible property exposes the boolean credit limit flag converted to type Visibility, so it becomes easy to bind the visibility of any element to this property without the need of a converter.
- The added properties also need to bind and update properly when one of the underlying data elements changes.
- The sales regions list is exposed as a Regions property on every view model.
Note: #1 means means that one has to implement INotifyPropertyChanged on the view model in order to get proper binding behavior. POCOs (plain old CLR objects) with standard properties do not notify a bound element of a change in the property, thus not causing the UI element to update properly when the property’s value changes. This unfortunately is a major nuisance in creating anything in WPF/SL that binds, because it means you can’t just use any old data source and use it for binding, if there is any chance the source could change (which is almost always the case in business apps… although there are exceptions… in my example, I can easily bind a sales-region drop down to a standard list of regions, because my sample app never changes the regions once assigned). Long story short: Implementing INPC (INotifyPropertyChanged) is one of the driving forces behind the creation of view models. (Note that my naive customer view model does not implement this interface and hence binding does not work properly… this object is only there for performance comparisons).
Overview of Approaches
I implemented a variety of different view models. There is a naive approach that simply wraps the Customer object and adds the desired properties. This approach is not actually functional, but it serves as a performance yardstick. If you run the sample application however, you will notice that if you select this type of data source and you update fields such as FirstName, the behavior is incorrect in that the FullName does not change (and so forth…).
I then have a few view models that follow a relatively conventional approach. For one, I created a view model that duplicates all the properties found in the Customer class, but it also manually implements INPC so each property properly indicates when it has been updated. (This is an approach I often encounter in the wild). In addition, I added the desired additional properties. I also make sure that when one of the properties another property depends on is updated (such as FirstName for FullName), then not only do I notify of a change in that property, but I call NotifyChange() for the second property as well, thus fulfilling the behavior requirements we have. Note that I load these view models 3 different ways: 1) by manually copying the values from the model to the view-model, 2) by having a reflection-based mapper handle the task for me, and 3) by creating lambda expressions that handle the mapping task for me.
Another approach is to implement a view model based on the DependencyObject class, with each property in the view-model implemented as a dependency property. This theoretically offers some advantages, but it also is extremely tedious as a lot of code has to be written for each property. Plus, there are some significant drawbacks (see below).
Finally, I implemented a number of different view-model objects that are based on C#4’s dynamic features. I have an object that is derived from DynamicObject which implements its own “state bag” to store the property values. A simple approach simply does that and then adds the other desired properties manually. All the original values simply get mapped into the state bag by hand. The advantage of this is that once such an object is in place, one does not have to define properties anymore. Properties can simply be used and will be added on the fly if need be. Plus, the object can automatically handle INPC, so that headache is gone. Oh, and I added the ability to register property dependencies. This means that the object can natively know that when “SalesRegionId” changes, “SalesRegion” changes as well. In addition, one can add interesting conventions. In my example, I can call any property with a “_Visible” suffix, and the dynamic object will automatically attempt to convert the value to a Visibility type. (You could easily add many more such useful conventions).
In addition to the simple dynamic object, I implemented other variations on the theme. One only uses the state bag for new properties but uses the model object directly as the state bag by pulling data out of that object using reflection. An even more advanced version uses a combination of a sophisticated state bag and model container approach with directly integrated access conventions and dependency registration. Finally, I implemented a dynamic object that uses the dependency property system as its state bag in the hopes of the dependency property system being more optimized than simple name-value dictionaries.
The Results in a Nutshell
You can read a more detailed description of the results for each version below (with some additional thoughts of mine added). In short, I would say that each approach is workable performance-wise. I am loading a very large number of objects. Scaled down to the number of view models you are likely to load in real applications, one is tempted to say that either approach should easily be fast enough. (On the other hand, I think we need to get away from this line of thinking in the Microsoft world, as other companies – in particular Apple and Google – are showing us that performance and responsive/fast experiences are what users want to buy these days).
Not surprisingly, the more hand-coding you do, the better the result. However, the dynamic approach is also growing on me for its various benefits (see below). It is slower, but still probably fast enough, and the benefits are pretty interesting. Dependency property approaches are the biggest disappointment in my tests. They are a pain to deal with, and the advantages are mostly theoretic in nature. Some things would be neat, but just aren’t that big a deal. Other things are great in theory, but you won’t be able to really get that benefit due to other overhead. Plus, there simply are a few show-stoppers that make the dependency property approach a non-starter in my opinion.
Here is a quick overview of my results with 100,000 view models, for instantiation, then accessing every property on every model, and then running a select statement on the list of 100,000 models. I ran these tests on the PDC Tablet PC Microsoft gave away to all PDC attendees. I figure this is a middle-of the road machine, plus, changes are people reading this might have the same machine. I built the test project (which you can download here) in Release configuration and ran the resulting EXE out of the file system and not from within Visual Studio. But as always: Performance numbers are hard to take literal. The most interesting aspect of these numbers is the relative comparison between the different approaches.
|Manual Static ViewModel
|Reflection Mapped ViewModel
|Lambda Mapped ViewModel
|Simple Dynamic ViewModel
|Good Dynamic ViewModel
|Better Dynamic ViewModel
|Dependency Object ViewModel
|Dynamic Dependency ViewModel
Note: It is a lot easier to look at the results in a nicely formatted Excel spreadsheet, which you can download here. It also has quite a bit more detail and precision, and I did a lot of the tests multiple times, which is reflected in the spreadsheet, but not in the table above.
The results in a nutshell are this: Everything you do by hand is fast. Everything you automate is much slower. Reflection is an awful performance killer. Dynamic stuff is a memory hog and slow at the same time. Surprisingly, Dependency Property based solutions perform badly on all accounts (time and memory). That last one may be the biggest surprise in these tests, although I was also surprised how much memory dynamic objects consume. Obviously, combining dependency objects, dynamic objects, and reflection is the “triple-whammy” of bad performance.
However: On the whole, if you load view models one-by-one to edit data, as is the case in most scenarios, all these approaches should work fine performance-wise. And dynamic solutions are starting to grow on my, because of the many benefits they offer. (Note that dynamic view models currently do not work in Silverlight).
For those of you who are interested in a detailed analysis, here is detail on each and every row in the above table:
The Detailed Results
The Model isn’t really part of the tests. The Model is the underlying data the view models all use. I am creating is in-memory, and it is only meant as a simulated data source. The exact data in the model changes with every run, but each subsequently tested view model uses the same model data. I thought it might be interesting to randomize the data a bit and run tests repeatedly, just to see if anything changes (it really didn’t much).
The interesting aspects of the Model are:
- It takes just under 5/100’s of a second to create 100,000 model objects in memory
- Consumed memory for all my tests is just under (or at about) 6MB. This is an interesting number, as view models have to use or duplicate that data.
I did not perform any tests on the Model in terms of running property access or select statements. They should probably be very similar to accessing the manually mapped static view model (see below).
The Naive (Faulty) ViewModel
This view model approach is also included for comparison only. It is a view model that simply uses the underlying model for pass-through access. It doesn’t implement INotifyPropertyChanged (INPC) and thus does not function properly. This view model only adds the 3 properties that aren’t found in the model (FullName, SalesRegion, and HasCreditLimit_Visible) and also exposes the Regions property to provide easy access to sales regions information. All other properties on this view model simply pass access on to the actual model class.
This view model represents faulty view models I often see in the wild, created by developers who do not understand INPC. This view model could only be used for read-only scenarios, and even for such scenarios, there are better ways to do this. However, this view model gives us interesting insight in raw performance we could theoretically get out of a view model if we wouldn’t have to worry about things such as INPC and inter-property dependencies.
Here are the most interesting performance aspects:
- At just over 1/100th of a second, this view model loads the fastest of all view models, which is not surprising, since it only stores a reference to 2 other objects – the model and the regions – on construction.
- Consumes the least amount of memory of any model (just under 2MB), although 2MB of just storing object references is a bit on the hefty side. Clearly, more memory is allocated than just storing 200,000 object references (2 for each of the 100,000 view model instances).
- Accessing all properties is fast. Once again, among the fastest scenarios consistently. This tells us that accessing a property that accesses another object’s property (the view model property accessing the model property) is about as fast as accessing the property directly ( as is the case in the 3 mapped examples below). This may come as a bit of a surprise. Not earth-shaking, but it made me raise an eye-brow (both, actually, since I can’t do the single-brow-raise ;-)).
- No memory is consumed accessing all the properties (which is true for almost all scenarios I tested)
- Running a LINQ select statement over this view model was also among the fastest with minimal difference to the view models that held their property values directly (which is about as surprising as the property access result above).
- A small amount of memory is allocated (32KB) to run the select. This result is consistent across all select tests.
So all in all, this would be desirable results, except for the fact that this view model does not work :-).
Manual Static ViewModel
This view model is a hand-coded view model that duplicates all the properties from the model and then adds the 3 additional properties as well as a reference to the sales regions information. This view model also manually implements INPC on all properties, and manually notifies secondary properties of changes (such as FullName changed when FirstName changes). In the constructor of this model, I hand-coded copying each property value from the model into the corresponding property on the view model (a tedious task in real-world view models, which generally have a lot more properties than I had here).
This view model represents a classic view model that’s fully functional. It also represents the view model that is amongst the most labor-intensive to implement (only trumped in tedium by the dependency property view model) as a lot of hand-coding of unskilled code is required. On the upside, this approach performs well, both in time and memory consumed.
Here are the most important characteristics:
- Mapping property values into this view model takes about 3/100’s of a second and thus 3 times as long as the faulty view model which doesn’t do any mapping at all. A 3-fold increment is a lot, but on the other hand, considering how much work is done here, this performance is pretty good and certainly acceptable for most uses.
- This view model consumes just over 6.5MB of memory and is thus only about 10% more memory hungry than the model itself (a fact that is probably explained by storing a reference to the Regions information). This result is very much in line with what I would have expected.
- Accessing every single property in every single instance of this view model is marginally slower than the naive implementation above. Not by much, but still, it is consistently a few % slower. I am surprised by this, as I would have imagined accessing a property value right within the object should be slower than accessing the property value on another object. It should involve twice as many steps (or only half as many in this implementation). Nevertheless, repeated tests always made this come out slower. I can only imagine it has to do with the overall increased memory consumption of the app. (It should not have to do with implementing INPC, since I am not testing set operations here).
- No memory is consumed accessing all the properties.
- Running a LINQ select over this view model is marginally faster than the naive implementation. This is directly contradicting the property access result. I would imagine that being faster makes more sense. In any event: The difference is very small. Maybe I should stop worrying about it :-)
- The select allocates the obligatory 32KB of memory.
All in all, this view model implementation works very well and is what other implementations are measured by. However, implementing this guy by hand is an error-prone pain in the rear, which is really why we are investigating other options in the first place.
Reflection Mapped ViewModel
This is really the same view model as the previous, manually mapped one. The only difference is that instead of mapping each value in the constructor, I am using reflection to automatically map all the properties that can be found in both objects. I might be able to optimize this better especially for objects of the same type, but that was not the goal of this performance test. Either way, the conclusion is that this guy moves like molasses! Clocking in a 3.7 seconds of load time, this guy is far more than 100 times slower than the manually mapped version. Ouch! (Of course if all you do is load one of these at a time, that may still be OK for you).
So the most important characteristic of this guy:
- Loads by far the slowest of all tested view model approaches (3.7 seconds)
- All other performance figures are identical to the manually mapped view model above (which makes sense, since everything other than the constructor is the same).
So this guy is very slow, but we also eliminated all the code it took to populate the object. This is a significant benefit that may be more important than load-performance in some scenarios. Overall, I am disappointed with this result. I didn’t think this would be quite so slow.
Lambda Mapped ViewModel
This is another variation on the same view mode, but instead of using reflection to map the models, I use a list of lambda expressions. The result is a lot faster than reflection, which makes sense since lambda expressions are just a list of code segments that get executed full blast one after another, with the only performance penalty we pay over a completely manually coded entity being only iterating through a list that contains these expressions.
The result is an object that loads 6-7 times slower than the hand-coded approach. So it is still quite fast, but when you think about it, this is also quite a bit slower than the manual version. A difference that is especially significant when you consider that I’d be hard-pressed to tell you what the benefit is of this over the manual version. You end up writing just as much code, but it is more difficult to read. Of course, you do gain the advantage that these maps could be defined in a reusable fashion, in case that provides a benefit to your scenario (such as when you have a single model that is used for many very similar view models).
Here is the skinny:
- Loads pretty fast at 0.2 seconds, but is much slower than a hand-coded version and provides little benefit
- All other performance figures are identical to the manually mapped view model above.
So you might as well forget about this one. Not a lot of benefit. Slower. Just as much code to write. Perhaps this is interesting if you want to combine reflection mapping with lambdas so you can do more than a 1:1 mapping. All in all, there is not much that appeals to me here.
Simple Dynamic ViewModel
Since C#4.0, we can use dynamic language features, and I have done so here by creating a view model that inherits from DynamicObject. This gives me some interesting options. For one, you do not have to define properties, but you can simply assign them. I do so in the constructor of the model:
public SimpleDynamicCustomerViewModel(Customer customer, IEnumerable<SalesRegion> regions)
self.FirstName = customer.FirstName;
self.LastName = customer.LastName;
Note that the “self” pointer acts just like “this” would, except “self” is a member I created in the base class and it always exposes “this” as type dynamic, which gives me easy access to dynamic features. The name “self” is one of the 2 generally accepted ways to refer to the current object (languages usually use either “this” or “self”… other versions like VB’s “Me” are not as widely used), which is why I chose that name.
Anyway: When this code runs, the system sees that these properties do not actually exist and then resorts to calling a TrySetMember() method, which I overrode to accept the new value and store it in an internal state bag (implemented here as a Dictionary<string, object>). What is very nice about this approach is that I can automatically handle INPC in the TrySetMember() method, and simply notify for a change of whatever the name of the desired object was. Furthermore, I can keep a list of dependent properties and automatically notify interested subscribers of a change of those properties as well. This allows me to register all dependent properties (such as “FullName” having a dependency on “FirstName” and “LastName”) in the constructor of the view model. Using this approach, the nastiness of INPC is handled once and for all, and not just that, but the dependent-property feature is pretty useful and cool. There is a lot to like here.
But there also are things that are not as likable. For one, using this approach, I still have to map the properties of the model into the state bag of my dynamic view model. So that is a lot of code to write. But worse: This guy is a performance-slouch! It is slow, and it consumes a very large amount of memory. 73MB to encapsulate what was originally 6MB! Not good. I understand that there is overhead in the dictionary (which stores not just the property value, but also its name in memory) and there is the dictionary with the dependency maps. But still, I do not understand why this consumes such an immense amount of memory. Something odd is going on here that warrants further investigation.
Here’s the overview of the performance characteristics:
- Load time is pretty bad. 2 seconds to load the 100,000 objects. For a version that basically manually maps the members into a state bag, that is a lot of time. (Although once again, if you only load a handful of these guys, or even a few thousand, you are probably perfectly fine).
- Memory consumption is insane! 73MB to manage 6MB of actual data?!? “Ain’t noth’n good coming from that!” (In general, it seems that instances of the Dictionary type are extremely memory intensive. I could probably mess with the object’s capacity and such, but I doubt a lot of people would do that in the real world, so I decided against such optimizations for this test. (And frankly, I would not expect it to make a huge difference).
- Accessing every property on every instance is not lightning fast, but it isn’t bad either. About 0.6 seconds to go through the entire test. So it’s about 6 times slower than static property access. I find that quite acceptable for a dynamic system, and you shouldn’t have a problem with that out in the wild. (Note that you will access properties much more often than you instantiate the object… especially in binding scenarios).
- Memory consumption for property access is odd. I generally see no memory used, but during the first iteration, I sometimes see memory being allocated (in the neighborhood of 300KB). It is an interesting phenomenon, but since we just blew 73MB on creating the object, I am not too worried about using 300KB during first time property access. It’s a rounding error by comparison.
- The LINQ select test runs generally around 7 or 8 hundreds of a second. That’s about 15 times slower than a static object. Sounds like a lot, but it is still very fast. We are selecting 10,000+ objects fro a set of 100,000 in 0.07 seconds. That is practically instantaneous and should not be a problem for any app. (If that is what your bottle-neck is, then dynamic is not the way to go for you anyway…)
- The select test usually also allocates the usual 32KB of memory, although there also sometimes are slight upward spikes around the 45KB mark. Once again, not something to be concerned about, but this sort of thing just tickles my curiosity.
So speed and memory management is not a strong point of this setup. However, there are some very very interesting benefits here. Despite the performance issues, I am very tempted by this approach. Never having to worry about INPC again, and even being able to define related properties is very cool. And there are more interesting features dangling there as a carrot, which we will explore below. In short: You wouldn’t pick this approach if you need performance and can’t waste memory, but at least there are huge upsides that may make it all worth it.
Note: Another downside of all dynamic view model approaches is that you can’t just look at the object to know what properties it has, since the properties are not explicitly defined. So when you need to write the binding expressions in your XAML view, you just need to know what is there, by looking in the constructor, or – my preferred option – by showing a custom debug tool (which you have to create yourself… but it is easy) that allows you a glimpse of the state bag.
Note: Also consider that dynamic view models currently only work in WPF, as Silverlight currently (v4) does not support binding to dynamic objects.
Good Dynamic ViewModel
The dynamic view model described above is a very simple implementation of a dynamic object. The concept however offers a number of other potential benefits. In this second dynamic approach, I added 2 interesting features: First, I am not using a dictionary as the state bag anymore, but I am using the actual model object as the state bag. Second, I am allowing for convention based property access. Here’s what this means in detail:
Instead of using a Dictionary<string, object>, I now simply pass the original model object to the constructor of my abstract view model class. When a property then is accessed that is not explicitly defined on the view model, TryGetMember() kicks in and looks at the original model object using reflection, to find out whether it has the desired property. If so, it simply accesses it. (This also works for TrySetMember(), allowing write access to the property). The advantage of this approach is that we now eliminate all mapping, since all the properties of the original model object are always accessible automatically. Furthermore, when a value is set, the TrySetMember() method does all the INPC stuff I described in the simple dynamic view model. Thus this approach simply decorates the original object fully automatically, with automatic INPC and also with support for related property notification. Very nice. Unfortunately, also very slow. :-(
The second feature I added is the convention based property access. I am only supporting a single convention here (a “_Visible” suffix), but one could take this idea quite far in very useful ways. The basic idea is that if someone accesses any property and adds “_Visible” to the name, special access happens and TryGetMember() tries to convert the original property to type “Visibility”. In our example, our model has a “HasCreditLimit” property, thus, if someone binds to “HasCreditLimit_Visible”, even though that property doesn’t really exist, the dynamic object will take the boolean value and turn it into a WPF Visibility type. (Write access works as well). I *love* this feature. I can add several more of these conventions and thus have 95% of all the properties I will ever need in my view model automatically covered by the features this dynamic view model object offers out of the box. This is extremely cool and saves a ton of time, reduces tedium, and eliminates a potential source of errors.
Well, at least it saves time writing code. It certainly doesn’t save time once the code runs, because this is slooooow to access properties. Drastically slower than any other option I evaluated. But it takes less memory than other dynamic objects, and it loads fast, since no mapping has to be done on load. (Although considering that fact, it is amazing why it takes as long as it does… probably due to storing property relation information, which could be optimized by putting values into a static definition, which I didn’t do for this test).
Here are the numbers:
- It takes 0.865 seconds to load this version. 2 1/2 times faster than the first dynamic version.
- It consumes a lot less memory than the first dynamic version (46MB vs. 73MB) but I am still floored about this massive consumption. I removed the property mapping data from this test, and it turns out it accounts for most of the allocated memory, so if this was kept in a static instance, memory consumption could be brought down to almost nothing, assuming you instantiate the same view model a lot.
- Accessing every single property in every single instance is extremely slow. Around 4.2 to 4.4 seconds for the 100,000 objects. More than 40 times slower than our static view model implementation. This is an operation that is done a lot, so this isn’t good. (On the other hand, once again, this is probably easily fast enough for most view model uses).
- Generally, no memory is consumed when accessing these properties, although I have seen odd random memory allocations (up to 70KB) for this operation. Nothing to worry about, but it seems to be clear that anything dynamic in C#/.NET seems to have this random minor memory allocation characteristic.
- Since property access is slow, one would expect the select test to be slow too, and that is exactly the case. With 0.3 seconds on that test, it is about 60 times slower than the hand-coded view model.
- For the most part, the select statement seems to allocate the usual 32KB of memory, although on occasion, consumption goes slightly higher.
All in all, performance is not great, but memory consumption could probably be optimized to a point where it was very good. The benefits of this approach are plain awesome, and if you are only instantiating a handful of objects for editing, or even a few hundred or thousand objects for a list, then this approach probably works very well and will save you a ton of time. If you have the need to have a lot of objects in memory on the other hand, and are binding all properties, this approach is not for you. (But then I would ask why you would really keep 100,000 objects in memory in the first place… especially in distributed scenarios, just getting the data from the database is going to be a serious drag, making the performance overhead of the dynamic object insignificant).
Note: The same property discoverability issue as with the simple dynamic object exists here.
Better Dynamic ViewModel
This is yet another improvement on the previous approach. In fact, I have a few ideas that I may continue to explore and add to my description here. The idea here is to go back to a state-bag approach and combine the first and the second view model approach. The state bag however is not just a simple Dictionary<string, object> but the value element is a specialized StateBagValue object, which encapsulates dependency information and potentially a lot more. For instance, this state bag could be used to cache reflection information. This would negatively impact memory consumption over time, but improve performance drastically, especially in scenarios where one accesses property values repeatedly (as in binding scenarios, which are obviously very common in view models, since that is why we build view models in the first place).
I have to spend a bit more time on this approach and will then publish my findings here. You can already take a look at the code I have in the current example. Current performance characteristics are very similar to the good dynamic view model described above.
Dependency Object ViewModel
This approach is pretty interesting. The idea here is to build view models entirely out of dependency objects with dependency properties. In theory, this approach has multiple advantages. For one, the dependency property system is highly optimized, as it was originally built for WPF interfaces that may have thousands of objects with lots and lots of properties. So the idea behind this view model approach is that if view models are potentially large lists of objects with lots of properties, then the same benefits should be useful here. Furthermore, dependency properties offer automatic change notification (so we do not have to implement INPC). Also – and this is unique to this approach – dependency properties are great for binding. You could for instance animate properties in a view model (and I can think of multiple uses for that) and you could also theoretically bind properties to each other, thus creating dependencies between properties.
In reality, none of this really is all that great. I hand-coded a view model with all dependency properties. This in concept is similar to the hand-coded static view model (see above), but using dependency properties instead of regular properties. Let me tell you: This was a pain! Lots of code to write, and most people would probably have a hard time telling what the code really is/does. “Close to business values coding”, this is not!
Furthermore, I would have expected object instantiation on this to be fast, but it was not! 2.2 seconds to instantiate all the objects puts it at second slowest. Only the reflection mapped static view model was slower. In fact, this is more than twice as time consuming as the good dynamic versions, and slightly slower than the most naive dynamic approach. It is more than 200 times slower than the standard hand-crafted view model. I am really not entirely sure why this would be. I understand that there is extra work that has to be done for dynamic properties (although my example uses the dependency property system in such a simple way, I am surprised there is much overhead), but I thought most of it would be handled statically, and thus loading 100,000 objects of the same type should be fast. Well, it isn’t!
Also, this guy gobbles up massive amounts of memory. 23MB to store 6MB of data. This one really floored me, and was probably the biggest surprise in all my tests. I always looked at dependency properties as a highly memory-optimized way to store property values, but I guess that benefit only kicks in when properties are set to their default value. Since very few properties in my view model are set to their default value (what would be the default value of a FirstName property other than an empty string? And how many names to you store in your database where the first name is really empty? Well, in my example, none), we do not get that benefit. Still, I am surprised that the required memory is 23MB and not 6MB. Frankly, this is useless for our purposes, since it doesn’t provide this desired benefit at all.
Also, the ability to bind individual properties together is something I have never found to be useful in any real world scenario. After all, you would probably have to write value converters and all kinds of stuff (to bind HasCreditLimit to HasCreditLimit_Visible for instance). There re simpler ways to do this, plus, if I wanted to write value converters, I could just use those in my views. Nope, this really isn’t all that useful.
There also is some speculation about dependency properties providing an advantage in binding to the UI. Frankly, I have no idea how I would test binding performance reliably. (Beside, I have never seen an app that was slow due to binding to POCO properties). I am under the impression that this should only make a difference if the object and property one binds *to* is a dependency property, as the binder couldn’t possibly know that the set/get on the source object is backed by a dependency property. Maybe I am missing something here, but I would not think this should make a difference. But of someone knows more about this, I would love you to post a comment.
So really, only automatic change notification is nice to have, but frankly, with the amount of code one has to write to get dependency properties, it would be easier to implement INPC by hand.
There also is a complete show-stopper here, IMO. Dependency properties can only be accessed from the thread that created the property (usually the UI thread… otherwise, things get REALLY complex). This means that you can’t update your view model from a background thread, which is something a lot of modern apps have to do at this point, or at least you shouldn’t lock yourself out from doing this in the future. Right there, this approach is becoming useless to me.
If you are still interested, here is the detailed performance information:
- Loading is slow at 2.2 seconds…
- …and memory intensive at 23MB.
- Access on the other hand is fast. I was able to access all properties at around 0.26 seconds, which is only about 2 1/2 times slower than the manually coded POCO view model. Still, since the memory benefit isn’t there, what is the point in writing the extra code and taking the performance hit?
- Practically no memory is accessed for the property access (although I did witness a small allocation, around 70KB, on occasion during the first run).
- Select is also fast at around 0.022 seconds. This is still about 4 times slower than POCO selects though. No point in taking the hit.
- Memory allocation for the select is the usual 32KB
Yeah, no reason to do this. Don’t even try.
Dynamic Dependency ViewModel
OK, so the dependency object test didn’t work out so well, but I wanted to try this anyway: What happens if I create a dynamic object but use the dependency object system as a view model? So instead of using a dictionary TryGetMember() and TrySetMember() could go to a dependency object and start registering and using dependency properties on it. It actually works perfectly fine. But it is also slow. Of course this approach eliminates the need to hand-code dependency properties, so it takes away that pain point, but this also means that there is a single state-bag class that can register a property of a certain name only once, which means that the type of it has to be “object” to eliminate potential duplicates. The only other way around that I can think of is to create new dependency objects for each dynamic object type you create, which would result in nasty code and people would probably be extremely confused about it all.
I am not even going to bore you with more details. This just doesn’t work very well at all. Here are the performance numbers:
- Loading is about the same as with the other dependency object approach (2.15 seconds)
- Memory consumption is through the rood! Not quite as bad as the 73MB the simple dynamic object approach took, but 60MB to load. Almost 3 times as much as the simple dependency object approach. (Once again, keeping that list of related properties in each instance seems to be the difference here… so I could optimize that by making that static…)
- Property access performance is that great. Just under a second to run the access test. That’s 4 times slower than the default dependency property. I guess for dynamic access that increase is OK though.It is about 10 times slower than the hand-coded POCO view model. All in all, this makes for the second slowest access time. Only the complex dynamic approach is slower.
- Just like in the previous example, usually no memory is allocated for the property access test. Sometimes we get the ominous 70KB allocation though.
- As we’d expect, the select statement was slower as well. 0.15 seconds to run the select test makes it the second slowest contender there also.
- Memory allocation is the usual 32KB, although I have seen slightly higher allocations, which seems to be par for the course on dynamic objects.
Anyway: Don’t do this. This just doesn’t work as I had speculated (not too surprising, after the simple dependency object view model performed so badly…).
Posted @ 12:17 AM by Egger, Markus (email@example.com)