Last active
July 19, 2023 08:30
-
-
Save SelvinPL/2ded2ecc464c4f086117941a11ed3802 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
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
I dug into this a bit more, and ended up here: dotnet/runtime#89170