Performance with loaded containers

Aug 8, 2011 at 3:26 PM

I've recently conducted a test of performance of various .Net DI containers (more details here: http://philipm.at/2011/0808/).

When loaded with about 222 interfaces and 666 classes (either configured as singletons or transient), Unity took 60 seconds to resolve 100k calls; that was 368 times slower than Autofac or similar containers.

The source code for my tests is here: https://github.com/philipmat/di_speed

Any thoughts on what can cause such abysmal performance?

Aug 9, 2011 at 9:37 PM

My first guess would be (not having looked @ the benchmark code) that the way we copy policy lists to ensure thread safety is interacting pathologically with your test, resulting in a lot of garbage and thus overhead in the GC.

That's just a guess.

Aug 10, 2011 at 5:40 AM
Edited Aug 10, 2011 at 4:40 PM

The problem seems to lie with IsRegistered which takes a disproportionate amount of time (32,347ms vs Resolve 580ms): http://twitter.com/roryprimrose/status/101150276437811200

The code I used was:

public void Run() {
  if (uc.IsRegistered<IDummy>())
    uc.Resolve<IDummy>().Do();
  else...
}

Aug 10, 2011 at 7:24 PM

Yeah, IsRegistered is not optimized at all - in fact, it's almost deliberately suboptimal, because if you're using it a lot you're most likely doing DI wrong.

Why do you call IsRegistered at all?

 

Aug 11, 2011 at 7:29 AM

I think its usage in the tests I mentioned stemmed from how we are using Unity: we have a good deal of optional plug in points and we thought using IsRegistered would be cheaper/faster/better/stronger :) than handling ResolutionFailedExceptions. 

From a semantic point of view, IsRegistered seemed a better choice. After all, in our usage - but I should think this is spirit with Microsoft's recommendation on using exceptions, it's not an execution failure case for an optional  plugin/interface to have no class registered for it. So I was happy to see Unity 2.0 introduce IsRegistered because it seemed to make the code more explicit.

If I was wrong, I'd appreciate a word of guidance, and that brings me to the next question: what is the purpose of IsRegistered then?

I think the necessity to check for presence of implementations can surely be mitigated by a Null Object Pattern approach. It just seemed excessive to provide no-op classes for every possible instance. But that might just be the laziness talking, so let's assume I did that (or decided to handle ResolutionFailedException against my preference). But if that's the right way to go, then the question becomes even more pertinent: why offer an IsRegistered at all, in particular one that is "deliberately suboptimal", as you put it?

Thank you very much.

Aug 11, 2011 at 2:46 PM
Edited Aug 11, 2011 at 3:24 PM

IsRegistered is intended for debugging your apps only. As Chris points out, this is for the sitations when you want to do a quick  “do I have this optional service” kinds of resolve calls.

Aug 11, 2011 at 3:04 PM

Thank you, Grigori. 

Is that "insider information" because neither the documentation page nor the actual source code mention such limitation:?

http://msdn.microsoft.com/en-us/library/ff662021(v=PandP.20).aspx

For my edification, could you point me to any resources that I can reference in my advice to other developers?

Thanks.

Aug 11, 2011 at 5:53 PM

Well, it should be pretty obvious from the source code:

        public static bool IsRegistered(this IUnityContainer container, Type typeToCheck, string nameToCheck)
        {
            Guard.ArgumentNotNull(container, "container");
            Guard.ArgumentNotNull(typeToCheck, "typeToCheck");

            var registration = from r in container.Registrations
                               where r.RegisteredType == typeToCheck && r.Name == nameToCheck
                               select r;
            return registration.FirstOrDefault() != null;
        }

We grab the Registrations collection and iterate through it. This is going to be O(N) in the number of registrations for each resolve. So yeah, I'd expect it to be slow.

How many explicit calls to Resolve are you doing? If you're calling a bunch of resolves to pull explicitly from the container, you're doing service locator, and that's a bigger problem than IsRegistered being slow. Did you know that Unity 2.0 has optional dependencies? You could use that instead, and avoid the call to IsRegistered and to Resolve.

Aug 11, 2011 at 6:40 PM
ctavares wrote:

We grab the Registrations collection and iterate through it. This is going to be O(N) in the number of registrations for each resolve. So yeah, I'd expect it to be slow.

How many explicit calls to Resolve are you doing? If you're calling a bunch of resolves to pull explicitly from the container, you're doing service locator, and that's a bigger problem than IsRegistered being slow. Did you know that Unity 2.0 has optional dependencies? You could use that instead, and avoid the call to IsRegistered and to Resolve.

Right, and I think the performance hit is not as much in looping performed in IsRegistered as it's in the building of Registrations, and furthermore probably in UnityContainer.FillTypeRegistrationDictionary. In these particular tests I have 666 classes registered, and I am calling IsRegistered, and by extension Registrations, 10k and respectively 100k times to get those numbers (~6 seconds for the 10k, 60 seconds for the 100k).

I wasn't aware of the optional registrations, thank you for pointing it out. You're right, it would be an appropriate solution for a great deal of cases; might not work for us because we like to keep the DI aspects, such as specific attributes, out of our code (even if that might sound a bit irrational). To top it off, in those specific places we make use of a ServiceLocator and a hierarchy of keyed child containers, and that's why we resort to Resolve<T>(key). Again, I saw IsRegistered as a way to avoid handling exceptions in this fashion.

Thank you very much - I have a bit of thinking to do and I need to come up a new test plan that will be closer to how most people probably use Unity while maintaining parity with the other containers so that the comparisons are valid.

Aug 11, 2011 at 9:03 PM

Optional dependencies can be configured the same way as any other dependency - through attributes, the API, or the config file. As such, you don't have to put attributes in your code anywhere. Just resolve an object with the optional dependencies, and that object will get them injected (or not) if the container has a registration for it.

-Chris

Aug 19, 2011 at 6:56 AM

I've updated my performance tests: http://philipm.at/2011/0819/

Unity posts some really good numbers when unencumbered by registration checks. Optional dependency resolution is quite speedy too.

I think the lesson I've learned is that when using Unity one needs to make sure that all dependencies resolve so that there's no need  to resort to IsRegistered or worse, have to handle the crazy expensive ResolutionFailedException.

Thank you very much for your help. I hope to see both IsRegistered and RFEx improved because I really enjoy using Unity.

May 4, 2013 at 12:36 AM
A note here for those using Unity.MVC3. It appears to be using IsRegistered() and thus suffers from the performance issues described above.
2013-05-03 20:08:41,630 [220] ERROR Portal.Services.LogService - Collection was modified; enumeration operation may not execute.
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.KeyCollection.Enumerator.MoveNext()
   at Microsoft.Practices.Unity.UnityContainer.FillTypeRegistrationDictionary(IDictionary`2 typeRegistrations)
   at Microsoft.Practices.Unity.UnityContainer.FillTypeRegistrationDictionary(IDictionary`2 typeRegistrations)
   at Microsoft.Practices.Unity.UnityContainer.get_Registrations()
  __ at Microsoft.Practices.Unity.UnityContainerExtensions.IsRegistered(IUnityContainer container, Type typeToCheck, String nameToCheck)
   at Microsoft.Practices.Unity.UnityContainerExtensions.IsRegistered(IUnityContainer container, Type typeToCheck)
   at Unity.Mvc3.UnityDependencyResolver.IsRegistered(Type typeToCheck)__
   at Unity.Mvc3.UnityDependencyResolver.<GetServices>d__0.MoveNext()
   at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
   at System.Linq.Enumerable.<ConcatIterator>d__71`1.MoveNext()
   at System.Linq.Enumerable.<SelectManyIterator>d__14`2.MoveNext()
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.OrderedEnumerable`1.<GetEnumerator>d__0.MoveNext()
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.Enumerable.<ReverseIterator>d__a0`1.MoveNext()
   at System.Web.Mvc.FilterProviderCollection.<RemoveDuplicates>d__b.MoveNext()
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.Enumerable.<ReverseIterator>d__a0`1.MoveNext()
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at System.Web.Mvc.FilterInfo..ctor(IEnumerable`1 filters)
May 4, 2013 at 5:08 PM
FYI, Unity3 comes with a package Unity.MVC that bootstraps ASP.NET MVC4. It's been properly tested (including perf testing).
Jun 25, 2013 at 3:52 PM
Can we use the package aforementioned with Unity 2.1 and .NET MVC3 running on .NET4 Fwk? I don't think Unity.Mvc3 will get an update to fix the issue mentioned with IsRegistered and I got the feeling that changing the single Unity.MVC library will be easier and safer than changing whole .NET fwk and MVC library.
Thanks!
Jun 26, 2013 at 6:43 AM
The short answer is no: Unity bootstrapper for ASP.NET MVC uses targets .NET 4.5, Unity 3 and MVC 4.

You could get the Unity bootstrapper for ASP.NET MVC (Unity.MVC) source code and (try to) compile against Unity 2.1 and MVC 3.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
Jun 26, 2013 at 12:06 PM
Yeah.. that's what I thought and ended up doing. The code is working smoothly since yday and now issues where detected since then.
Thanks for the quick replay!!