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.