C#/.NET Method Call Performance - Facts
Wondering what’s the difference in performance for different method types in C#? Is performance the same for static, dynamic, virtual methods? What about asynchronous calls?
Here are the raw performance measurements:
Method | Mean | Error | StdDev | Min | Max | Median | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|---|
Static | 0.1086 ns | 0.6070 ns | 0.0333 ns | 0.0835 ns | 0.1464 ns | 0.0960 ns | - | - | - | - |
StaticViaLambda | 4.1860 ns | 1.1548 ns | 0.0633 ns | 4.1383 ns | 4.2578 ns | 4.1619 ns | - | - | - | - |
StaticAsync | 20.0131 ns | 31.6018 ns | 1.7322 ns | 18.0743 ns | 21.4084 ns | 20.5566 ns | 0.0172 | - | - | 72 B |
Dynamic | 0.4950 ns | 0.6165 ns | 0.0338 ns | 0.4564 ns | 0.5189 ns | 0.5098 ns | - | - | - | - |
DynamicAsync | 17.9998 ns | 16.0884 ns | 0.8819 ns | 17.4451 ns | 19.0167 ns | 17.5376 ns | 0.0172 | - | - | 72 B |
Virtual | 0.6042 ns | 0.3357 ns | 0.0184 ns | 0.5860 ns | 0.6228 ns | 0.6038 ns | - | - | - | - |
VirtualAsync | 20.8322 ns | 5.6442 ns | 0.3094 ns | 20.4769 ns | 21.0419 ns | 20.9778 ns | 0.0172 | - | - | 72 B |
That’s eye opening to be honest.
Summary
- Static methods are 6 times faster than normal instance methods.
- Static lambda call is 38 times slower than static method call.
Why? This is really odd, but not when you look at generated IL. Here’s one for static method call:
MethodCalls.Static:
IL_0000: ldc.i4.1
IL_0001: call UserQuery+MethodsContainer.StaticMethod
IL_0006: ret
and for static lambda:
MethodCalls.StaticViaLambda:
IL_0000: ldsfld UserQuery+MethodCalls._staticLambda
IL_0005: ldc.i4.1
IL_0006: callvirt System.Func<System.Int32,System.Int32>.Invoke
IL_000B: ret
As you can see call
is replaced with callvirt
- so static lambda is actually treated as virtual method call. Thus we have an overhead of ldsfld
loading wrapper instance as well.
- Static methods are 68 times faster than virtual methods.
- Virtual methods are 10.5 times slower than instance methods. Makes you think to carefully choose which methods should be virtual.
- Async calls allocate 72 bytes of memory, regardless of method signature. Normal methods have no impact on memory allocations.
- Regardless of method signature, all of the async method calls are really slow.
- The slowest method signature (virtual async) is 2293 (two thousand two hundred ninety three) times slower than the fastest method signature (static method).
Appendix. Performance Testing Source Code.
#LINQPad optimize+
void Main()
{
Util.AutoScrollResults = true;
BenchmarkRunner.Run<MethodCalls>();
}
[ShortRunJob]
[MinColumn, MaxColumn, MeanColumn, MedianColumn]
[MemoryDiagnoser]
[MarkdownExporter]
public class MethodCalls
{
private MethodsContainer _c = new MethodsContainer();
private static Func<int, int> _staticLambda = MethodsContainer.StaticMethod;
[Benchmark]
public int Static()
{
return MethodsContainer.StaticMethod(1);
}
[Benchmark]
public int StaticViaLambda()
{
return _staticLambda(1);
}
[Benchmark]
public async Task<int> StaticAsync()
{
return await MethodsContainer.StaticAsyncMethod(1);
}
[Benchmark]
public int Dynamic()
{
return _c.DynamicMethod(1);
}
[Benchmark]
public async Task<int> DynamicAsync()
{
return await _c.DynamicAsyncMethod(1);
}
[Benchmark]
public int Virtual()
{
return _c.VirtualMethod(1);
}
[Benchmark]
public async Task<int> VirtualAsync()
{
return await _c.VirtualAsyncMethod(1);
}
}
public class MethodsContainer
{
public static int StaticMethod(int arg) { return arg * 2; }
public static Task<int> StaticAsyncMethod(int arg) { return Task.FromResult<int>(arg * 2); }
public int DynamicMethod(int arg) { return arg * 2; }
public Task<int> DynamicAsyncMethod(int arg) { return Task.FromResult<int>(arg * 2); }
public virtual int VirtualMethod(int arg) { return arg * 2; }
public virtual Task<int> VirtualAsyncMethod(int arg) { return Task.FromResult<int>(arg * 2); }
}
To contact me, send an email anytime or leave a comment below.