Contributing a ViewLocator performance improvement

Topics: Extensibility, Feature Requests
Jul 7, 2013 at 3:24 AM
I've done some performance analysis on our CM-based solution and view location in a viewmodel-first often rises to the top of a hotspot analysis. The problem is predominantly the O(n2) join on exported types from the assemblies in AssemblySource.Instance within ViewLocator.LocateTypeForModelType. Since the performance was problematic for our application, I needed to improve it.

The first problem I encountered is that the logic to select types from names is tightly bound within the LocateTypeForModelType method. After the generation of view type names from viewmodel type names in TransformNames, it seems natural to break out the discovery of types by name into another extension point, so I created FindTypesByName. This would allow me to create some kind of caching mechanism on type names so I don't need to keep hitting the tight join on GetExportedTypes.

After adding FindTypesByName to ViewLocator, I started caching types by name in a Dictionary<String, Type>. This mostly worked but if the cache started empty, I still had to hit GetExportedTypes at least once for each view type. If I pre-populated the cache with all exported types from AssemblySource.Instance the cache size was large (16,000+ entries) and I imagine this would put undue pressure on memory constrained devices. After a few rounds of measuring and adjusting, I think I found a happy midpoint in caching custom derived types of UIElement eagerly on cache creation with a single call on GetExportedTypes. This results in a 300+ entry cache which, considering an average type name length of 40 characters is ~5000 bytes - a reasonable cache size for everything except smart watches (which CM doesn't run on, yet!). The load time for the initial cache creation turned in times 50% faster and subsequent calls to LocateTypeByModelType didn't even register in the profiler's hotspot analysis after this change.

I committed this code to a branch on my fork for any review before I make a push request.
Jul 10, 2013 at 6:33 AM
Really impressive.

What do you think about adding the FindTypeByNames to AssemblySource, so ViewModelLocator can benefit from caching too.
Jul 10, 2013 at 6:49 AM
That's a good suggestion. I realized this was too local of a change after some reflection, but I hadn't considered moving FindTypesByNames. AssemblySource does seem like a nice fit for it.

My current thinking is it would probably be overall beneficial to move the caching into a separate class whose methods can be plugged into (by default, perhaps) the LocateType* properties of ViewLocator and ViewModelLocator. This could then also expose methods for optionally dealing with cache invalidation and/or purging or perhaps custom preloading, depending on app needs, without overburdening the locators with cache responsibilities. Since a cache with no control is essentially a memory leak, it's important to allow developers to control the behavior, and warrants the addition of another type to represent this behavior, even while wisely trying to keep the framework "micro".
Jul 10, 2013 at 9:05 AM
I have added FindTypesByNames to AssemblySource (without caching) to create the extension point.
Looking forward to your caching ideas.
Jul 10, 2013 at 5:11 PM
What do you think about this version?
    public static class TypeNameCache {
        public static readonly IDictionary<String, Type> Cache = new Dictionary<string, Type>();

        public static Func<IEnumerable<string>, Type> FindTypeByNames = names => {
            if (names == null) {
                return null;

            var nameList = names.ToList();
            var type = nameList.Where(n => Cache.ContainsKey(n)).Select(n => Cache[n]).FirstOrDefault();
            if (type != null) {
                return type;

            type = nameList
                .Join(AssemblySource.Instance.SelectMany(a => a.GetExportedTypes()), n => n, t => t.FullName, (n, t) => t)

            if (type != null) {
                Cache[type.FullName] = type;

            return type;

        public static System.Action PreFillCache = () => {
#if !WinRT
            var excluded = new[] {
                typeof(Object).Assembly,            // mscorlib; System
                typeof(IQueryable).Assembly,        // System.Core; System
                typeof(IAttachedObject).Assembly,   // System.Windows.Interactivity
                typeof(UIElement).Assembly,         // PresentationCore; System.Windows
                typeof(FrameworkElement).Assembly   // PresentationFramework; System.Windows
            var excluded = new[]
                    typeof (Object).GetTypeInfo().Assembly,
                    typeof (UIElement).GetTypeInfo().Assembly, // Windows.UI.Xaml
            var types = AssemblySource.Instance
                                      .Where(a => !excluded.Contains(a))
                                      .SelectMany(a => a.GetExportedTypes())
                                      .Where(t =>
                                             typeof (UIElement).IsAssignableFrom(t) ||
                                             typeof (INotifyPropertyChanged).IsAssignableFrom(t));

            types.Apply(t => Cache.Add(t.FullName, t));
It can be used instead of the simple AssemblySource.FindTypeByNames.
Oct 20, 2013 at 9:20 AM
Should caching of type-name lookup be part of the core framework?
Oct 26, 2013 at 10:05 AM
I think simple, restrained caching should be. If it's separate, then it requires extra configuration.
Oct 29, 2013 at 4:47 AM
Nov 2, 2013 at 6:05 PM
Integrated a new version of type-name caching that does not require any other code to change.
Also an assembly is only queried once when it is added to AssemblySource.Instance collection.

Hope this will work for you.