This is pretty strange for such a huge overheard. There are a few things to take into account. First the VS compiled code has different properties applied to it that might influence the jitter to optimize differently.
Are you including the first execution for the compiled delegate in these results? You shouldn’t, you should ignore the first execution of either code path. You should also turn the normal code into a delegate as delegate invocation is slightly slower than invoking an instance method, which is slower than invoking a static method.
As for other changes there is something to account for the fact that the compiled delegate has a closure object which isn’t being used here but means that this is a targeted delegate which might perform a bit slower. You’ll notice the compiled delegate has a target object and all the arguments are shifted down by one.
Also methods generated by lcg are considered static which tend to be slower when compiled to delegates than instance methods because of register switching business. (Duffy said that the “this” pointer has a reserved register in CLR and when you have a delegate for a static it has to be shifted to a different register invoking a slight overhead).
Finally, code generated at runtime seems to run slightly slower than code generated by VS. Code generated at runtime seems to have extra sandboxing and is launched from a different assembly (try using something like ldftn opcode or calli opcode if you don’t believe me, those reflection.emited delegates will compile but won’t let you actually execute them) which invokes a minimal overhead.
Also you are running in release mode right?
There was a similar topic where we looked over this problem here:
Why is Func<> created from Expression<Func<>> slower than Func<> declared directly?
Also see my answer here:
DynamicMethod is much slower than compiled IL function
The main takeaway is that you should add the following code to the assembly where you plan to create and invoke run-time generated code.
And to always use a built-in delegate type or one from an assembly with those flags.
The reason being that anonymous dynamic code is hosted in an assembly that is always marked as partial trust. By allowing partially trusted callers you can skip part of the handshake. The transparency means that your code is not going to raise the security level (i.e. slow behavior), And finally the real trick is to invoke a delegate type hosted in an assembly that is marked as skip verification.
Func<int,int>#Invoke is fully trusted, so no verification is needed. This will give you performance of code generated from the VS compiler. By not using these attributes you are looking at an overhead in .NET 4. You might think that SecurityRuleSet.Level1 would be a good way to avoid this overhead, but switching security models is also expensive.
In short, add those attributes, and then your micro-loop performance test, will run about the same.