Ayende @ Rahien

It's a girl

Creating objects - Perf implications

Here are a few examples of how we can create objects, and the perf implications of each way. In all those tests, I have used the following class as my benchmark.

public class Created
{
    public int Num;
    public string Name;
 
    public Created(int num, string name)
    {
        this.Num = num;
        this.Name = name;
    }
}

We have a value type and a reference type that are passed to the constructor.

The test code is here:

static void Main(string[] args)
{
    int iterations = 1000000;
    Stopwatch watch = Stopwatch.StartNew();
    for (int i = 0; i < iterations; i++)
    {
        CreateInstance(i);
    }
    Console.WriteLine(watch.Elapsed);
}

Here it my base line, calling the constructor directly.

private static Created CreateInstance(int i)
{
    return new Created(i, i.ToString());
}

This executes in 00.3648117 seconds on severely underpower laptop. Pretty good, considering we just created a million instances. Now, let us see what happens if we user Activator, shall we?

private static Created CreateInstance(int i)
{
    return (Created)Activator.CreateInstance(
                        typeof(Created), i, i.ToString());
}

This run depressingly slow, 06.8242636 seconds.

Let us try to improve that a bit, using GetUninitializedObject and directly invoking the constructor.

static ConstructorInfo ctor = typeof(Created).GetConstructors()[0];
private static Created CreateInstance(int i)
{
    object o = FormatterServices.GetUninitializedObject(typeof(Created));
    return (Created)ctor.Invoke(o, new object[]{i, i.ToString()});
}

This runs in 03.2422335 seconds, a significant improvement, but we can do even more, I think.

We start by making the required definitions:

static ConstructorInfo ctor = typeof(Created).GetConstructors()[0];
delegate Created CreateCtor(int i, string s);
static CreateCtor createdCtorDelegate;

Then we generate a dynamic method to create the object, and turn that into a delegate:

DynamicMethod method = new DynamicMethod("CreateIntance", typeof(Created),
    new Type[] { typeof(int), typeof(string) });
ILGenerator gen = method.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);// i
gen.Emit(OpCodes.Ldarg_1);// s
gen.Emit(OpCodes.Newobj, ctor);// new Created
gen.Emit(OpCodes.Ret);
createdCtorDelegate = (CreateCtor)method.CreateDelegate(typeof(CreateCtor));

Now, in the method itself, all we have to do is call this delegate:

private static Created CreateInstance(int i)
{
    return createdCtorDelegate(i, i.ToString());
}

And now that run very fast... 00.4314517 seconds. Almost as fast as our baseline. But this is not really a good example, I am afraid. At least, not a god example of generally creating instances, let us make this into the more general form, shall we?

We will change the CreateCtor delegate to the following signature:

delegate object CreateCtor(object[] args);

And the generation of the dynamic method to use the generic approach:

DynamicMethod method = new DynamicMethod("CreateIntance", typeof(Created),
    new Type[] { typeof(object[]) });
ILGenerator gen = method.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);//arr
gen.Emit(OpCodes.Ldc_I4_0);
gen.Emit(OpCodes.Ldelem_Ref);
gen.Emit(OpCodes.Unbox_Any, typeof(int));
gen.Emit(OpCodes.Ldarg_0);//arr
gen.Emit(OpCodes.Ldc_I4_1);
gen.Emit(OpCodes.Ldelem_Ref);
gen.Emit(OpCodes.Castclass, typeof(string));
gen.Emit(OpCodes.Newobj, ctor);// new Created
gen.Emit(OpCodes.Ret);
createdCtorDelegate = (CreateCtor)method.CreateDelegate(typeof(CreateCtor));

We need to make the following modification to our CreateInstance method:

private static Created CreateInstance(int i)
{
    return (Created)createdCtorDelegate(new object[]{i, i.ToString()});
}

And now it runs in.... 00.5018288.

Now, what does this long and arcane post tells us?

Creating instances, no matter how many, is really cheap. Remember that I had to do a million iteration to get something measurable.

The table bellow give the final statistics. Pay attention to the last column, this gives the amount of time it take to create a single instance. Even the most hard core perf fanatic would be hard pressed to argue over 0.000007 seconds. At least I hope so :-)

Create single instance Iterations Time Method
0.00000036481170000000 1000000
0.36481
new
0.00000682426360000000 1000000
6.82426
Activator
0.00000324223350000000 1000000
3.24223
GetUninitializedObject
0.00000050182880000000 1000000
0.50183
Dynamic Method

And just to clarify, you are not going to see anything like creating a million object in most scenarios that you care about. In other words, you can probably leave things well enough alone.

Comments

Chad Myers
02/27/2008 04:11 PM by
Chad Myers

The extra 0.000007 seconds is probably in the call of the delegate, and not in the instance create.

What if you called the delegate directly from the loop instead of calling a func that calls another func?

Ayende Rahien
02/27/2008 04:13 PM by
Ayende Rahien

Which 0.00007 seconds are you talking about? That is the cost of creating an object using Activator.CreateInstance

Chad Myers
02/27/2008 07:59 PM by
Chad Myers

Ayende: Oops, I was talkin' about the diff between the Dynamic Method and calling 'new' directly.

The diff/instance of those is 0.0000001370171 seconds

Bill Barry
02/27/2008 08:37 PM by
Bill Barry

How would you generate a method to call a constructor which has more than 3 arguments?

Ayende Rahien
02/27/2008 09:23 PM by
Ayende Rahien

Chad,

I refuse to care about that kind of number. :-)

Bill Barry
02/27/2008 09:50 PM by
Bill Barry

I meant with the opcodes:

ILGenerator gen = method.GetILGenerator();

gen.Emit(OpCodes.Ldarg_0);// i

gen.Emit(OpCodes.Ldarg_1);// s

gen.Emit(OpCodes.Newobj, ctor);// new Created

there is:

OpCodes.Ldarg_0

OpCodes.Ldarg_1

OpCodes.Ldarg_2

OpCodes.Ldarg_3

but there isn't an OpCodes.Ldarg_4. How would you load one with more arguments? I figure those are shortcuts the compiler could use, but you should be able to generate a function which returns a "create instance" delegate with more arguments. I was trying to see if I could write a dynamic method with this signature:

delegate Created CreateCtor(params object[] s);

Ayende Rahien
02/27/2008 09:59 PM by
Ayende Rahien

There is

generator.Emit(OpCodes.Ldarg, 5)

Bill Barry
02/27/2008 10:00 PM by
Bill Barry

umm, err, ignore that last comment; that is basically what you did (I was thinking generic referred to .NET Generics).

I feel dumb sometimes.

James Arendt
02/28/2008 01:12 AM by
James Arendt

Another option is available in .NET 3.5. Build a lambda expression tree and compile it into a delegate. No need to write Emit statements as in the dynamic IL approach.

Tor Langlo
02/28/2008 05:17 AM by
Tor Langlo

Just curious, how long does the "i.ToString()" call take in this test?

Martin
02/28/2008 07:31 AM by
Martin

Do you have figures when creating transient objects with IoC.Resolve? Just curious..

Jon Skeet
02/28/2008 08:32 AM by
Jon Skeet

One interesting twist would to be running the same tests, but passing in a constant string. You're not creating 1 million objects here - you're creating at least 2 million.

I haven't run all of your tests, but just the "call the constructor" one: and on my laptop, creating 100 million instances goes down from taking 28.6 seconds to 0.97 seconds when you pass in "x" as the string parameter. Turning off method inlining pushes it up to 1.46 seconds.

In other words, at first sight (and I'm aware that benchmarking is a horribly fiddly business) the actual object creation is only taking about 5% of the time. The rest is the constant hit of converting an integer into a string.

Given that information, I think it's worth rerunning all the tests. Suddenly all the overheads of delegates will look more significant, and Activator.CreateInstance will be an absolute dinosaur - I suspect.

None of this detracts from your point that creating objects is indeed very cheap.

Roger Alsing
02/28/2008 08:45 AM by
Roger Alsing

I've posted an alternative to these approaches using Linq expression trees:

http://rogeralsing.com/2008/02/28/linq-expressions-creating-objects/

Mark Seemann
02/28/2008 12:03 PM by
Mark Seemann

Thank you for teaching me something new today :)

What's the overhead associated with creating the dynamic method?

When you create a million objects, it's probably neglegible, but what if you are only creating a few? Is there a break-even where, below that number of objects, one of the other methods is more efficient, but for a higher number, a dynamic method is the best performer?

Ayende Rahien
02/28/2008 02:50 PM by
Ayende Rahien

Martin,

I haven't checked.

Ayende Rahien
02/28/2008 03:48 PM by
Ayende Rahien

Mark,

Creating a new dyamic method: 00.0012487

Creating a single object using Activator.CreateInstance: 0.0000063033382

I wouldn't worry about it

dominique Gratpain
03/01/2008 10:43 AM by
dominique Gratpain

What about if you use properties (with accessors get/set) instead of public variables (Num and Name) ?

Have you test the same with vb.net ?

I am curious about these two points.

Thanks

Ayende Rahien
03/01/2008 03:00 PM by
Ayende Rahien

dominique ,

Not really relevant, because it is object creation we are testing.

Adam Tybor
03/02/2008 12:08 AM by
Adam Tybor

Interesting.... I get Security.VerificationException at run time. I was working on a patch for MicroKernel DefaultActivator and I can't even get your simple code to run. Is it a 3.5 thing?

Operation could destabilize the runtime.

System.Security.VerificationException: Operation could destabilize the runtime.

at CreateIntance(Int32 , String )
Ayende Rahien
03/02/2008 12:12 AM by
Ayende Rahien

That happens when you get the code even slightly wrong

Comments have been closed on this topic.