Last active
October 25, 2024 15:07
-
-
Save Eideren/b379bb5db698afb9c847138735e039a3 to your computer and use it in GitHub Desktop.
Revisions
-
Eideren revised this gist
Oct 25, 2024 . 1 changed file with 3 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -32,16 +32,10 @@ public interface ICommonInterface public static int MyInt; } public sealed class ConcreteA : ICommonInterface { public int DoTheThing(int i) => ICommonInterface.MyInt += i + 1; } public sealed class ConcreteB : ICommonInterface { public int DoTheThing(int i) => ICommonInterface.MyInt += i + 2; } public sealed class ConcreteC : ICommonInterface { public int DoTheThing(int i) => ICommonInterface.MyInt += i + 3; } public abstract class Elider { protected abstract void Add(ICommonInterface obj); @@ -53,8 +47,8 @@ public static void AddToHandlers(ICommonInterface item, Dictionary<Type, Elider> var concreteType = item.GetType(); if (handlers.TryGetValue(concreteType, out var batcher) == false) { var gen = typeof(Handler<>).MakeGenericType(concreteType); // Create one specific Handler<> for this concrete type batcher = (Elider)Activator.CreateInstance(gen)!; // Create an instance of this specialized type to pass our items to it handlers.Add(concreteType, batcher); } batcher.Add(item); @@ -71,7 +65,7 @@ public static void DoTheThing(Dictionary<Type, Elider> handlers) kvp.Value.DoTheThing(); } private sealed class Handler<T> : Elider where T : ICommonInterface // This class get specialized to a concrete type { private List<T> _abstraction = []; protected override void Add(ICommonInterface obj) => _abstraction.Add((T)obj); -
Eideren created this gist
Oct 25, 2024 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,12 @@ | Method | ItemsCount | Mean | Error | StdDev | Min | Max | Rank | |------- |----------- |--------------:|-------------:|-------------:|---------------:|--------------:|-----:| | Elided | 10 | 12.09 ns | 0.043 ns | 0.034 ns | 12.050 ns | 12.14 ns | 2 | | Naive | 10 | 10.04 ns | 0.089 ns | 0.079 ns | 9.936 ns | 10.20 ns | 1 | | Elided | 100 | 92.30 ns | 0.786 ns | 0.735 ns | 90.977 ns | 93.58 ns | 3 | | Naive | 100 | 153.22 ns | 1.041 ns | 0.923 ns | 152.210 ns | 155.50 ns | 4 | | Elided | 1000 | 808.97 ns | 8.076 ns | 7.555 ns | 797.855 ns | 823.80 ns | 5 | | Naive | 1000 | 1,550.42 ns | 4.637 ns | 4.110 ns | 1,545.038 ns | 1,559.88 ns | 6 | | Elided | 100000 | 169,402.00 ns | 834.769 ns | 740.001 ns | 168,302.930 ns | 170,912.40 ns | 7 | | Naive | 100000 | 497,252.59 ns | 2,017.683 ns | 1,575.274 ns | 493,172.534 ns | 498,835.08 ns | 8 | As you can see, the performance improvement can't be felt below 10 items, there's too much overhead before then. This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,85 @@ using System; using System.Collections.Generic; public class ExampleUsage { public void Elided() { // Let's say you have a ton of objects inheriting from the same class or interface ... var myObjects = new List<ICommonInterface>(); // You want to call a method over each one of them but are worried about the cost those virtual calls would introduce ... foreach (var myAbstractObject in myObjects) myAbstractObject.DoTheThing(10); // This would go through the VTable to find the right method to call, and also prevents any inlining // You can instead break up each object by type ... var eliders = new Dictionary<Type, Elider>(); foreach (var myAbstractObject in myObjects) Elider.AddToHandlers(myAbstractObject, eliders); // ... and force the JIT to create one new type per type instance // Then it's just a case of having a purpose built Elider for each case you may need, // This sounds ripe for abstraction, sadly you can't really abstract as you would want because generalizing // this prevents the JIT/PGO from inlining appropriately, leading to worse performance than the naive version. Elider.DoTheThing(eliders); } } public interface ICommonInterface { public int DoTheThing(int i); public static int MyInt; } // Which is inherited by a bunch of different objects // You want to call functions of those components but are worried about the cost incurred from calling virtual functions ... public sealed class ConcreteA : ICommonInterface { public int DoTheThing(int i) => ICommonInterface.MyInt += i + 1; } public sealed class ConcreteB : ICommonInterface { public int DoTheThing(int i) => ICommonInterface.MyInt += i + 2; } public sealed class ConcreteC : ICommonInterface { public int DoTheThing(int i) => ICommonInterface.MyInt += i + 3; } // public abstract class Elider { protected abstract void Add(ICommonInterface obj); protected abstract bool Remove(ICommonInterface obj); protected abstract void DoTheThing(); public static void AddToHandlers(ICommonInterface item, Dictionary<Type, Elider> handlers) { var concreteType = item.GetType(); if (handlers.TryGetValue(concreteType, out var batcher) == false) { var gen = typeof(Handler<>).MakeGenericType(concreteType); batcher = (Elider)Activator.CreateInstance(gen)!; handlers.Add(concreteType, batcher); } batcher.Add(item); } public static bool RemoveFromHandlers(ICommonInterface item, Dictionary<Type, Elider> handlers) { return handlers.TryGetValue(item.GetType(), out var handler) && handler.Remove(item); } public static void DoTheThing(Dictionary<Type, Elider> handlers) { foreach (var kvp in handlers) kvp.Value.DoTheThing(); } private sealed class Handler<T> : Elider where T : ICommonInterface { private List<T> _abstraction = []; protected override void Add(ICommonInterface obj) => _abstraction.Add((T)obj); protected override bool Remove(ICommonInterface obj) => _abstraction.Remove((T)obj); protected override void DoTheThing() { foreach (var abstraction in _abstraction) abstraction.DoTheThing(10); } } }