Skip navigation.

European EnglishAll recent postsBe Careful With Cached User Controls

Performance of Dynamic Object Instantiation

As Provider pattern has been gaining more and more favor among developers, I’ve been wondering about performance implications of dynamic object (provider) instantiation it mandates. The most common mechanism to create providers on the fly has been via Activator.CreateInstance. Steve Maine did a nice perf test of Activator vs. the new operator, which showed that Activator was quite slow (which was reasonable to expect).

In addition to Steve’s tests, I thought of one more way to instantiate an object dynamically: to cache its constructor and then invoke it repeatedly to give life to new objects.

Suppose Target is some simple class. We can get to its constructor (parameterless or not), call Invoke and cast it to Target.

Type type = typeof (Target);
ConstructorInfo constructor = type.GetConstructor (new Type[] {});
Target t = (Target) constructor.Invoke (null);

I use this approach on my site, except that I cache the constructor and only call Invoke every time.

public static IDataProvider Instance ()
{
 Cache cache = HttpRuntime.Cache;
 if (null == cache ["DataProvider"]) 
 {
  ...
  Type type = Type.GetType (providerTypeName);
  cache.Insert ("DataProvider", type.GetConstructor (new Type[] {}));
  ...
 }
 return (IDataProvider)(((ConstructorInfo) 
         cache["DataProvider"]).Invoke (null));
}

To see how this approach faired against Activator.CreateInstance I put together a console test app to measure instantiation time of 1,000,000 objects via new, Activator.CreateInstance and constructor caching. I took a shortcut and adapted Eric Gunnerson’s test app he created to measure performance of dynamic code invocation.

Test Duration
InstantiateDirect 0.02303477 sec
InstantiateWithActivatorWithGetType 1.66457 sec
InstantiateWithActivatorWithGetTypePrefetched 1.661113 sec
InstantiateWithCachedConstructor 1.237231 sec

I used a complimentary copy of Rich Chart Builder to create a chart with the same test results:

Results of dynamic object instantiation benchmark

Creating 1,000,000 instances of a simple class directly took only 0.023 sec, while other types of instantiation were significantly heavier. Prefetching the constructor helped a little bit and beat Activator. This is pretty much what I expected, but it was interesting to see the tally nonetheless.

If you want to poke under the hood, feel free to grab source code.

Comments

Comment permalink 1 Jeff Gonzalez |
I wonder if you implemented ICloneable on the type you would be dynamically creating and cached the default created instance instead if you would get better performance.

I suppose it wouldn't be as dynamic, and I suppose it depends on the requirements of your application. Do you typically have several data providers from a single application?

If not I think you could create the type with Activator.CreateInstance at appstart, clone it immediately, store it in cache and then just clone the cached instance.
Comment permalink 2 Jeff Gonzalez |
So I did a quick test...

I implemented ICloneable on your Target class

public object Clone()
{
return this.MemberwiseClone();
}

Then I added the following code above your result loop:

Type type = typeof (Target);
Target t = (Target) Activator.CreateInstance (type);
Hashtable cachedTypes = new Hashtable();
cachedTypes.Add("Target", t);

This code inside the for loop:
Target realType = ((ICloneable)cachedTypes["Target"]).Clone() as Target;

These are the results I got, my additional test is called 'InstantiateWithCachedActivatorWithGetType' :

InstantiateDirect: 0.03551513
InstantiateWithActivatorWithGetType: 0.8604999
InstantiateWithActivatorWithGetTypePrefetched: 0.8212122
InstantiateWithCachedConstructor: 0.9590532
InstantiateWithCachedActivatorWithGetType: 0.2592056

Not quite as fast as direct instantiation, but I think the performance gain was pretty good. Let me know what you think.
Comment permalink 3 Milan Negovan |
I think you're on to something interesting. ;) I need to read up on how shallow and deep cloning works because I'm a bit rusty on this.
Comment permalink 4 Dave Bacher |
Cache a delegate, instead of the constructor.

[Provider("name")]
class myProviderFactory
{
public static baseProvider CreateInstance()
{
return (baseProvider) new myProvider();
}
}

public delegate baseProvider CreateInstanceDelegate();

...

MethodInfo mi = factory.GetMethod("CreateInstance", BindingFlags.Static);
CreateInstanceDelegate cid = System.Delegate.CreateDelegate(CreateInstanceDelegate, mi);
return cid();

...



Part of the issue with Invoke is that it still has to perform reflection in order to perform its call.

Another fast mechanism is:
System.AppDomain.CurrentDomain.CreateInstance("name", false);
(doing it by the System.Type is even faster)

This removes the need to cache at all, if you can handle a 1 to 1 correlation between provider names and your class.
Comment permalink 5 KVH |
.Net 2.0 Dual Core 3.19GHz

InstantiateDirect: 0,02239186
InstantiateWithActivatorWithGetType: 0,2616625
InstantiateWithActivatorWithGetTypePrefetched: 0,2451015
InstantiateWithCachedConstructor: 1,805805
InstantiateWithCurrentDomainCreateInstanceAndUnwrap: 67,52738

From current domain is very slow. (67,52738)
Target t = (Target)(AppDomain.CurrentDomain.CreateInstance(type.Assembly.FullName, type.FullName)).Unwrap();
Comment permalink 6 Philip |
Reflector shows that AppDomain.CurrentDomain.CreateInstance still uses the System.Activator. Not sure why it's so much slower than the other Activator benchmarks.

Phil
Comment permalink 7 Pete |
use a DynamicMethod to generate a cached delegate (by type), then call that delegate upon each instantiation of a particular class. It's as close as I could get to a pure call performance (~+15%) which ain't bad at all.

http://tinyurl.com/339p9k
Comment permalink 8 Philip |
A very fast way, almost as fast as direct, is to use System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type As System.Type).

I did a write-up on it here: http://www.codeproject.com/useritems/DynamicFactoryDemo.asp
Comment permalink 9 Matt Dockerty |
Thanks for this article. After reading it, I've rolled my own version which uses caching and direct instantiation. I've posted it below in case it's any help.

Presuming you have a handful of potential objects in your application and you don't mind the requirement of an empty constructor, this solution should be the quickest overall. It calls Activator.CreateInstance on first request and then directly instantiates objects thereafter. You can also prepare the cache ahead of time in a less performance-critical section of your code such as on startup.

public static class CachedActivator {
static CachedActivator() {
genericFactoryType = typeof(GenericFactory);
}

public static void PrepareCache(Type t) {
if(!cachedFactories.ContainsKey(t)) {
cachedFactories.Add(t, new Factory(t));
}
}

public static object CreateInstance(Type t) {
PrepareCache(t);
return cachedFactories[t].Create();
}

#region Embedded Classes

private class Factory {
public Factory(Type t) {
Type initialisedFactoryType =
genericFactoryType.MakeGenericType(t);

this.genericFactory =
(GenericFactoryBase)Activator.CreateInstance(initialisedFactoryType);
}

public object Create() {
return this.genericFactory.CreateObject();
}

private GenericFactoryBase genericFactory;
}

private abstract class GenericFactoryBase {
public abstract object CreateObject();
}

private class GenericFactory : GenericFactoryBase where T : class, new() {
public override object CreateObject() {
return this.Create();
}

public T Create() {
return new T();
}
}

#endregion

#region Private Members

private static Type genericFactoryType;
private static Dictionary cachedFactories =
new Dictionary();

#endregion
}
Comment permalink 10 Matt Dockerty |
Lol, your blog ate my generics! Imagine a (T) in the right places and you won't go far wrong.

Emails and Notifications

Would you like to be notified when somebody responds to this post?  Would you like to have these comments emailed to you?

TrackBacks

Sorry, TrackBacks are not allowed.

Submit your comment

Please enter only text since all HTML tags except hyperlinks will be stripped. Hyperlinks will become live links. Any comments with flaming or offensive language will be deleted. Be courteous to other posters. Thank you.

Your name (required):
Your email (optional):
Your site's URL (optional):
Enter this number
Type in the number above:
Comment (required):