Skip to content

Benchmarks

Sample benchmarks of function hooks.

Hardware

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-4790K CPU 4.00GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.200-preview.20601.7
  [Host]     : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT
  Job-EJHKEF : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT

Platform=X64  Toolchain=64 bit cli  

Benchmarked Methods

The benchmark measures the time taken to execute the following function 1 million (1,000,000) times:

static int Add(int a, int b) { return a + b; }

|             Method | Description
|------------------- | -----------
|     ManagedInlined | Execute an inlined version of the code.
|            Managed | Execute a non-inlined version of the code.
| ManagedUnoptimized | Execute an unoptimized version of the code. (Calling Convention Compliant)
|      NoHookPointer | Execute a native assembly version of the code using C#9 function pointers (calli)
|     NoHookDelegate | Execute a native assembly version of the code using Delegates (Marshal.GetDelegateForFunctionPointer)
|       DelegateHook | Execute a hooked version using delegates.
|        FuncPtrHook | Execute a hooked version using function pointers.

ManagedInlined stack:

  • .net (function)

Managed stack:

  • .net (implementation)
  • .net (function)

ManagedUnoptimized stack:

  • .net (implementation, calling convention compliant)
  • .net (function)

NoHookPointer stack:

  • native (via function pointer)
  • .net to native transition
  • .net (function)

NoHookDelegate stack:

  • native (via function pointer)
  • .net to native transition
  • .net delegate code
  • .net (function)

DelegateHook stack:

  • native
  • .net to native transition
  • .net delegate code
  • .net (hook function)
  • .net delegate code
  • native to .net transition
  • native
  • .net to native transition
  • .net delegate code
  • .net (function)

FuncPtrHook stack:

  • native
  • .net to native transition
  • .net (hook function)
  • native to .net transition
  • native
  • .net to native transition
  • .net (function)

X86 Benchmarks

|             Method |        Mean |     Error |    StdDev |
|------------------- |------------:|----------:|----------:|
|     ManagedInlined |    448.7 μs |   2.89 μs |   2.71 μs |
|            Managed |  1,754.0 μs |   9.40 μs |   7.34 μs |
| ManagedUnoptimized |  1,992.6 μs |  20.04 μs |  17.76 μs |
|      NoHookPointer |  2,661.3 μs |  16.87 μs |  14.96 μs |
|     NoHookDelegate | 10,787.7 μs | 123.28 μs | 115.32 μs |
|       DelegateHook | 41,242.1 μs | 226.90 μs | 201.14 μs |
|        FuncPtrHook | 11,825.8 μs |  54.23 μs |  48.07 μs |

X64 Benchmarks

|             Method |        Mean |     Error |    StdDev |
|------------------- |------------:|----------:|----------:|
|     ManagedInlined |    448.6 μs |   1.90 μs |   1.78 μs |
|            Managed |  1,583.8 μs |   8.50 μs |   7.53 μs |
| ManagedUnoptimized |  1,820.4 μs |  20.18 μs |  15.75 μs |
|      NoHookPointer |  6,958.4 μs |  20.41 μs |  17.04 μs |
|     NoHookDelegate | 16,784.0 μs | 111.56 μs | 104.35 μs |
|       DelegateHook | 55,987.3 μs | 456.96 μs | 405.08 μs |
|        FuncPtrHook | 24,694.2 μs | 157.92 μs | 123.29 μs |

Notes

  • A close estimate of the native to .net transition can be calculated with NoHookPointer - ManagedUnoptimized.
  • Calling a function using a delegate is almost as expensive as full function hook using function pointers.
  • If absolute performance is a must (e.g. hooking graphics APIs called 100,000+ times per second), you should probably use native code instead.
  • Some improved performance may be seen in the future based on a possible implementation of CallConvSuppressGCTransition.