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:

MethodMeanErrorStdDevMinMaxMedianGen 0Gen 1Gen 2Allocated
Static0.1086 ns0.6070 ns0.0333 ns0.0835 ns0.1464 ns0.0960 ns----
StaticViaLambda4.1860 ns1.1548 ns0.0633 ns4.1383 ns4.2578 ns4.1619 ns----
StaticAsync20.0131 ns31.6018 ns1.7322 ns18.0743 ns21.4084 ns20.5566 ns0.0172--72 B
Dynamic0.4950 ns0.6165 ns0.0338 ns0.4564 ns0.5189 ns0.5098 ns----
DynamicAsync17.9998 ns16.0884 ns0.8819 ns17.4451 ns19.0167 ns17.5376 ns0.0172--72 B
Virtual0.6042 ns0.3357 ns0.0184 ns0.5860 ns0.6228 ns0.6038 ns----
VirtualAsync20.8322 ns5.6442 ns0.3094 ns20.4769 ns21.0419 ns20.9778 ns0.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); }
}


To contact me, send an email anytime or leave a comment below.