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
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?
Which 0.00007 seconds are you talking about? That is the cost of creating an object using Activator.CreateInstance
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
How would you generate a method to call a constructor which has more than 3 arguments?
Chad,
I refuse to care about that kind of number. :-)
Bill
Um, the same way?
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);
There is
generator.Emit(OpCodes.Ldarg, 5)
umm, err, ignore that last comment; that is basically what you did (I was thinking generic referred to .NET Generics).
I feel dumb sometimes.
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.
Just curious, how long does the "i.ToString()" call take in this test?
Do you have figures when creating transient objects with IoC.Resolve? Just curious..
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.
I've posted an alternative to these approaches using Linq expression trees:
http://rogeralsing.com/2008/02/28/linq-expressions-creating-objects/
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?
Martin,
I haven't checked.
Mark,
Creating a new dyamic method: 00.0012487
Creating a single object using Activator.CreateInstance: 0.0000063033382
I wouldn't worry about it
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
dominique ,
Not really relevant, because it is object creation we are testing.
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.
That happens when you get the code even slightly wrong
Comment preview