Creating objects - Perf implications

time to read 23 min | 4569 words

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.