Reputation: 10959
This is an overly simplified example of something else I am trying to do, but, for now, consider these casting methods:
public static string StringTryCast(object o)
{
return o as string;
}
public static T RefTypeTryCast<T>(object o) where T : class
{
return o as T;
}
When I execute those in a loop of 50,000,000 iterations, I seem to get much slower times than if I execute the cast inline. Here are the four tests I am conducting with a comment that corresponds to the test cases below them.
object BoxedValue = "my string";
//inline trycast
() => { s = BoxedValue as string; }
//method: RefTypeTryCast
() => { s = RefTypeTryCast<string>(BoxedValue); }
//method: StringTryCast
() => { s = StringTryCast(BoxedValue); }
Here are the test results. I ran five tests of 50,000,000 iterations for each method and then calculated the average.
inline trycast 50,000,000x...
368 ms
370 ms
374 ms
380 ms
380 ms
374.4 ms average over 5 iterations
method: RefTypeTryCast 50,000,000x...
1083 ms
1098 ms
1100 ms
1133 ms
1138 ms
1110.4 ms average over 5 iterations
method: StringTryCast 50,000,000x...
477 ms
478 ms
487 ms
489 ms
493 ms
484.8 ms average over 5 iterations
At 50,000,000 iterations, inline trycast is...
1.2949 x Faster than method: StringTryCast
2.9658 x Faster than method: RefTypeTryCast
I cannot understand why StringTryCast
would perform any differently when it does an inline cast in a helper method. Adding [MethodImpl(MethodImplOptions.AggressiveInlining)]
to the method didn't appear to help. Furthermore, RefTypeTryCast
uses generics and performs 3x worse than inline.
It seems as though they should all perform relatively the same.
Edit: As mentioned in the comments, I use a helper class to run my tests. This is basically the encapsulated logic.
Stopwatch sw = new Stopwatch();
for (i = 0; i < 5; i++)
{
sw.Restart();
for (int o = 0; o < 50000000; o++)
{
Test(); //anon method passed in from lambda expression
}
sw.Stop();
times.Add(i, sw.ElapsedMilliseconds);
}
Upvotes: 0
Views: 46
Reputation: 966
This is for release build.
IL code for `StringTryCast` and `RefTypeTryCast`:
.method public hidebysig static !!T RefTypeTryCast<class T> (object o) cil managed
{
IL_0000: ldarg.0
IL_0001: isinst !!T
IL_0006: unbox.any !!T
IL_000b: ret
}
.method public hidebysig static string StringTryCast (object o) cil managed
{
IL_0000: ldarg.0
IL_0001: isinst [mscorlib]System.String
IL_0006: ret
}
As you can see, there is one more instruction in case of generic function: unbox.any
. From https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.unbox_any.aspx we can see that it does three operations: object is pushed to stack, popped and unboxed from stack and then pushed back to stack. So, there is more work done, hence more time.
For difference between inline and StringTryCast
, in case of inline, you have one pushing to stack, checking if what's on stack is string and then popping from stack.
For StringTryCast
, there is pushing to stack, calling method which pushes argument to stack, checks if it's string, returns it pushing it from its stack to caller's stack and then when it's back, it is popped from stack once more.
Again, more work -> more time.
Upvotes: 3