Skip to content

Instantly share code, notes, and snippets.

@SelvinPL
Last active July 19, 2023 08:30
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 SelvinPL/2ded2ecc464c4f086117941a11ed3802 to your computer and use it in GitHub Desktop.
Save SelvinPL/2ded2ecc464c4f086117941a11ed3802 to your computer and use it in GitHub Desktop.
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22621
12th Gen Intel Core i7-12700H, 1 CPU, 20 logical and 14 physical cores
[Host] : .NET Framework 4.8 (4.8.9167.0), X86 LegacyJIT
DefaultJob : .NET Framework 4.8 (4.8.9167.0), X86 LegacyJIT
| Method | Mean | Error | StdDev | Gen 0 | Allocated |
|
----------------------------------- |-----------:|----------:|----------:|-------:|----------:|
| CantonWithValueTuple | 777.0 ns | 5.18 ns | 4.84 ns | - | - |
| CantonAccelerationsClass | 551.9 ns | 10.18 ns | 9.52 ns | 0.3815 | 2,003 B |
| CantonAccelerationsStruct | 770.3 ns | 4.22 ns | 3.94 ns | - | - |
| NotifyCacheObjectPoolConcurrentBag | 7,665.9 ns | 151.98 ns | 142.16 ns | 0.3815 | 2,003 B |
| NotifyCacheObjectPoolStack | 905.6 ns | 11.04 ns | 10.32 ns | - | - |
| DefaultListenerInplace | 576.4 ns | 10.57 ns | 9.89 ns | 0.6132 | 3,217 B |
BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1992/22H2/2022Update/SunValley2)
12th Gen Intel Core i7-12700H, 1 CPU, 20 logical and 14 physical cores
.NET SDK=7.0.304
[Host] : .NET 6.0.20 (6.0.2023.32017), X64 RyuJIT AVX2
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|----------------------------------- |-----------:|---------:|---------:|-------:|----------:|
| CantonWithValueTuple | 1,012.3 ns | 7.72 ns | 7.22 ns | - | - |
| CantonAccelerationsClass | 478.1 ns | 3.97 ns | 3.71 ns | 0.2546 | 3200 B |
| CantonAccelerationsStruct | 285.0 ns | 4.40 ns | 4.11 ns | - | - |
| NotifyCacheObjectPoolConcurrentBag | 5,324.1 ns | 92.75 ns | 86.76 ns | - | - |
| NotifyCacheObjectPoolStack | 746.7 ns | 10.34 ns | 9.67 ns | - | - |
| DefaultListenerInplace | 614.7 ns | 5.42 ns | 4.80 ns | 0.5112 | 6424 B |
public class Program
{
public static void Main()
{
var summary = BenchmarkRunner.Run<Test>();
Console.WriteLine(summary);
}
[MemoryDiagnoser]
public class Test
{
Service service = new Service();
[Benchmark]
public void CantonWithValueTuple()
{
service.Reset();
Action<IAccelerometerListener, ValueTuple<int,int,int>> action = (listener, state) => listener.OnAccelerometersChanged(state.Item1, state.Item2, state.Item3);
for (int i = 0; i < 100; i++)
{
var accelerations = ValueTuple.Create(i, i + 1, i + 2);
service.Notify(action , accelerations);
}
}
[Benchmark]
public void CantonAccelerationsClass()
{
service.Reset();
Action<IAccelerometerListener, AccelerationsC> action = (listener, state) => listener.OnAccelerometersChanged(state.X, state.Y, state.Z);
for(int i = 0; i < 100; i++)
{
var accelerations = new AccelerationsC(i, i+1, i+2);
service.Notify(action , accelerations);
}
}
[Benchmark]
public void CantonAccelerationsStruct()
{
service.Reset();
Action<IAccelerometerListener, AccelerationsSR> action = (listener, state) => listener.OnAccelerometersChanged(state.X, state.Y, state.Z);
for (int i = 0; i < 100; i++)
{
var accelerations = new AccelerationsSR(i, i + 1, i + 2);
service.Notify(action, accelerations);
}
}
[Benchmark]
public void NotifyCacheObjectPoolConcurrentBag()
{
service.Reset();
for (int i = 0; i < 100; i++)
{
service.Notify(NotifyCache.Create(i, i + 1, i + 2));
}
}
[Benchmark]
public void NotifyCacheObjectPoolStack()
{
service.Reset();
for (int i = 0; i < 100; i++)
{
service.Notify(NotifySimpleCache.Create(i, i + 1, i + 2));
}
}
[Benchmark]
public void DefaultListenerInplace()
{
service.Reset();
for (int i = 0; i < 100; i++)
{
service.Notify(listener => listener.OnAccelerometersChanged(i, i + 1, i + 2));
}
}
}
}
//private record struct AccelerationsSR(int X, int Y, int Z);
private struct AccelerationsSR
{
public int X, Y, Z;
public AccelerationsSR(int X, int Y, int Z)
{
this.X = X;
this.Y = Y;
this.Z = Z;
}
}
public class AccelerationsC
{
public int X, Y, Z;
public AccelerationsC(int X, int Y, int Z)
{
this.X = X;
this.Y = Y;
this.Z = Z;
}
}
public class NotifySimpleCache
{
private static readonly ObjectPool<NotifySimpleCache> cache = new ObjectPool<NotifySimpleCache>(() => new NotifySimpleCache());
private int x, y, z;
private readonly Action<IAccelerometerListener> Call;
private NotifySimpleCache()
{
Call = listener =>
{
listener.OnAccelerometersChanged(x, y, z);
cache.Return(this);
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Action<IAccelerometerListener> Create(int x, int y, int z)
{
var item = cache.Get();
item.x = x;
item.y = y;
item.z = z;
return item.Call;
}
public class ObjectPool<T>
{
private readonly Stack<T> _objects;
private readonly Func<T> _objectGenerator;
public ObjectPool(Func<T> objectGenerator)
{
_objectGenerator = objectGenerator ?? throw new ArgumentNullException(nameof(objectGenerator));
_objects = new Stack<T>();
}
public T Get() => _objects.Count > 0 ? _objects.Pop() : _objectGenerator();
public void Return(T item) => _objects.Push(item);
}
}
public class NotifyCache
{
private static readonly ObjectPool<NotifyCache> cache = new ObjectPool<NotifyCache>(() => new NotifyCache());
private int x, y, z;
private readonly Action<IAccelerometerListener> Call;
private NotifyCache()
{
Call = listener =>
{
listener.OnAccelerometersChanged(x, y, z);
cache.Return(this);
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Action<IAccelerometerListener> Create(int x, int y, int z)
{
var item = cache.Get();
item.x = x;
item.y = y;
item.z = z;
return item.Call;
}
public class ObjectPool<T>
{
private readonly ConcurrentBag<T> _objects;
private readonly Func<T> _objectGenerator;
public ObjectPool(Func<T> objectGenerator)
{
_objectGenerator = objectGenerator ?? throw new ArgumentNullException(nameof(objectGenerator));
_objects = new ConcurrentBag<T>();
}
public T Get() => _objects.TryTake(out T item) ? item : _objectGenerator();
public void Return(T item) => _objects.Add(item);
}
}
class Service
{
TestListener listener = new TestListener();
public void Reset()
{
listener.Reset();
}
public void Notify<TState>(Action<IAccelerometerListener, TState> action, TState state)
{
action(listener, state);
}
public void Notify(Action<IAccelerometerListener> action)
{
action(listener);
}
}
private class TestListener : IAccelerometerListener
{
static long sum = 0;
void IAccelerometerListener.OnAccelerometersChanged(int x, int y, int z)
{
sum += x;
sum += y;
sum += z;
}
public void Reset()
{
sum = 0;
}
}
public interface IAccelerometerListener
{
void OnAccelerometersChanged(int x, int y, int z);
}
@canton7
Copy link

canton7 commented Jul 19, 2023

I dug into this a bit more, and ended up here: dotnet/runtime#89170

@canton7
Copy link

canton7 commented Jul 19, 2023

Using double instead of int:

Method Mean Error StdDev Gen0 Allocated
CantonWithValueTuple 382.1 ns 3.78 ns 3.53 ns - -
CantonAccelerationsClass 545.1 ns 9.56 ns 8.94 ns 0.3185 4000 B
CantonAccelerationsStruct 378.3 ns 5.47 ns 5.12 ns - -
NotifyCacheObjectPoolConcurrentBag 5,388.9 ns 84.41 ns 74.83 ns - -
NotifyCacheObjectPoolStack 755.9 ns 13.31 ns 11.80 ns - -
DefaultListenerInplace 694.4 ns 9.25 ns 8.65 ns 0.5112 6424 B

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment