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

  1. Static methods are 6 times faster than normal instance methods.
  2. 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.

  1. Static methods are 68 times faster than virtual methods.
  2. Virtual methods are 10.5 times slower than instance methods. Makes you think to carefully choose which methods should be virtual.
  3. Async calls allocate 72 bytes of memory, regardless of method signature. Normal methods have no impact on memory allocations.
  4. Regardless of method signature, all of the async method calls are really slow.
  5. 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); }
}
Have a question⁉ Contact me.