Skip to content

Instantly share code, notes, and snippets.

@ocoanet
Last active January 27, 2023 08:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ocoanet/6e43b220be54a9d74c51eb309507c226 to your computer and use it in GitHub Desktop.
Save ocoanet/6e43b220be54a9d74c51eb309507c226 to your computer and use it in GitHub Desktop.
InterfaceDispatchBenchmarks
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
namespace ConsoleApp_Bench_1;
public class InterfaceDispatchBenchmarks_StaticOrNonStatic
{
public static Accessor1[] Instances_Typed { get; } = Enumerable.Range(0, OperationsPerInvoke).Select(_ => new Accessor1()).ToArray();
public static IAccessor[] Instances_SingleType { get; } = Enumerable.Range(0, OperationsPerInvoke).Select(_ => (IAccessor)new Accessor1()).ToArray();
public static IAccessor[] Instances_MultiTypes { get; } = Enumerable.Range(0, OperationsPerInvoke).Select(CreateAccessor).ToArray();
private static IAccessor CreateAccessor(int i)
{
return (i % 4) switch
{
0 => new Accessor1(),
1 => new Accessor2(),
2 => new Accessor3(),
_ => new Accessor4(),
};
}
public const int OperationsPerInvoke = 100_000;
[Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
public int NonStatic_SingleType()
=> NonStatic_Impl(Instances_SingleType);
[Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
public int NonStatic_MultiTypes()
=> NonStatic_Impl(Instances_MultiTypes);
[MethodImpl(MethodImplOptions.NoInlining)]
private static int NonStatic_Impl(IAccessor[] accessors)
{
var sum = 0;
for (var i = 0; i < OperationsPerInvoke; i++)
{
var accessor = accessors[i];
sum += accessor.Read();
}
return sum;
}
[Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
public int Static()
=> Static_Impl(Instances_Typed);
[MethodImpl(MethodImplOptions.NoInlining)]
private static int Static_Impl<T>(T[] accessors)
where T : IStaticAccessor<T>
{
var sum = 0;
for (var i = 0; i < OperationsPerInvoke; i++)
{
sum += T.ReadStatic(accessors[i]);
}
return sum;
}
[Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
public int Static_Inlinable()
=> StaticInterface_Inline_Impl(Instances_Typed);
private static int StaticInterface_Inline_Impl<T>(T[] accessors)
where T : IStaticAccessor<T>
{
var sum = 0;
for (var i = 0; i < OperationsPerInvoke; i++)
{
sum += T.ReadStatic(accessors[i]);
}
return sum;
}
public interface IStaticAccessor<T>
{
static abstract int ReadStatic(T target);
}
public interface IAccessor
{
int Read();
}
public class Accessor1 : IStaticAccessor<Accessor1>, IAccessor
{
public int Value { get; set; }
public static int ReadStatic(Accessor1 target) => target.Value;
public int Read() => Value;
}
public class Accessor2 : IStaticAccessor<Accessor2>, IAccessor
{
public int Value { get; set; }
public static int ReadStatic(Accessor2 target) => target.Value;
public int Read() => Value;
}
public class Accessor3 : IStaticAccessor<Accessor3>, IAccessor
{
public int Value { get; set; }
public static int ReadStatic(Accessor3 target) => target.Value;
public int Read() => Value;
}
public class Accessor4 : IStaticAccessor<Accessor4>, IAccessor
{
public int Value { get; set; }
public static int ReadStatic(Accessor4 target) => target.Value;
public int Read() => Value;
}
}
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
namespace ConsoleApp_Bench_1;
/// <summary>
/// Measures the effect of invoking an interface method (static or non-static) at
/// the same call site with multiple types.
/// </summary>
/// <remarks>
/// The goal here is not to measure the exact costs but to identify if there is a
/// difference or not between the single type and the multiple types scenarios.
/// </remarks>
public class InterfaceDispatchBenchmarks_SingleTypeOrMultiTypes
{
public static IAccessor[] Accessors1 { get; } = Enumerable.Range(0, OperationsPerInvoke).Select(x => (IAccessor)new Accessor1()).ToArray();
public static IAccessor[] Accessors2 { get; } = Enumerable.Range(0, OperationsPerInvoke).Select(x => (IAccessor)new Accessor2()).ToArray();
public static IAccessor[] Accessors3 { get; } = Enumerable.Range(0, OperationsPerInvoke).Select(x => (IAccessor)new Accessor3()).ToArray();
public static Accessor1[] Accessors1_Typed { get; } = Enumerable.Range(0, OperationsPerInvoke).Select(x => new Accessor1()).ToArray();
public static Accessor2[] Accessors2_Typed { get; } = Enumerable.Range(0, OperationsPerInvoke).Select(x => new Accessor2()).ToArray();
public static Accessor3[] Accessors3_Typed { get; } = Enumerable.Range(0, OperationsPerInvoke).Select(x => new Accessor3()).ToArray();
public const int OperationsPerInvoke = 100_000;
[Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
public int NonStatic_SingleType()
=> NonStatic_Impl(Accessors1, Accessors1, Accessors1);
[Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
public int NonStatic_MultiTypes()
=> NonStatic_Impl(Accessors1, Accessors2, Accessors3);
[MethodImpl(MethodImplOptions.NoInlining)]
private static int NonStatic_Impl(IAccessor[] accessors1, IAccessor[] accessors2, IAccessor[] accessors3)
{
var sum = 0;
for (var i = 0; i < OperationsPerInvoke; i++)
{
sum += Read(accessors1[i]);
sum += Read(accessors2[i]);
sum += Read(accessors3[i]);
}
return sum;
// Forces a unique call site.
[MethodImpl(MethodImplOptions.NoInlining)]
static int Read(IAccessor accessor)
=> accessor.Read();
}
[Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
public int Static_SingleType()
=> Static_Impl(Accessors1_Typed, Accessors1_Typed, Accessors1_Typed);
[Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
public int Static_MultiTypes()
=> Static_Impl(Accessors1_Typed, Accessors2_Typed, Accessors3_Typed);
[MethodImpl(MethodImplOptions.NoInlining)]
private static int Static_Impl<T1, T2, T3>(T1[] accessors1, T2[] accessors2, T3[] accessors3)
where T1 : IStaticAccessor<T1>
where T2 : IStaticAccessor<T2>
where T3 : IStaticAccessor<T3>
{
var sum = 0;
for (var i = 0; i < OperationsPerInvoke; i++)
{
sum += Read(accessors1[i]);
sum += Read(accessors2[i]);
sum += Read(accessors3[i]);
}
return sum;
// Forces a unique call site.
[MethodImpl(MethodImplOptions.NoInlining)]
static int Read<T>(T accessor) where T : IStaticAccessor<T>
=> T.ReadStatic(accessor);
}
public interface IStaticAccessor<T>
{
static abstract int ReadStatic(T target);
}
public interface IAccessor
{
int Read();
}
public class Accessor1 : IStaticAccessor<Accessor1>, IAccessor
{
public int Value { get; set; }
public static int ReadStatic(Accessor1 target) => target.Value;
public int Read() => Value;
}
public class Accessor2 : IStaticAccessor<Accessor2>, IAccessor
{
public int Value { get; set; }
public static int ReadStatic(Accessor2 target) => target.Value;
public int Read() => Value;
}
public class Accessor3 : IStaticAccessor<Accessor3>, IAccessor
{
public int Value { get; set; }
public static int ReadStatic(Accessor3 target) => target.Value;
public int Read() => Value;
}
}

InterfaceDispatchBenchmarks_StaticOrNonStatic

|               Method |      Mean |     Error |    StdDev |
|--------------------- |----------:|----------:|----------:|
| NonStatic_SingleType | 1.6293 ns | 0.0153 ns | 0.0143 ns |
| NonStatic_MultiTypes | 4.9330 ns | 0.0985 ns | 0.1412 ns |
|               Static | 1.7086 ns | 0.0057 ns | 0.0053 ns |
|     Static_Inlinable | 0.5637 ns | 0.0100 ns | 0.0094 ns |

InterfaceDispatchBenchmarks_SingleTypeOrMultiTypes

|               Method |     Mean |     Error |    StdDev |
|--------------------- |---------:|----------:|----------:|
| NonStatic_SingleType | 6.953 ns | 0.0224 ns | 0.0209 ns |
| NonStatic_MultiTypes | 8.104 ns | 0.0279 ns | 0.0261 ns |
|    Static_SingleType | 9.668 ns | 0.0506 ns | 0.0474 ns |
|    Static_MultiTypes | 9.984 ns | 0.0663 ns | 0.0620 ns |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment