Skip to content

Instantly share code, notes, and snippets.

@Eideren
Last active October 25, 2024 15:07
Show Gist options
  • Save Eideren/b379bb5db698afb9c847138735e039a3 to your computer and use it in GitHub Desktop.
Save Eideren/b379bb5db698afb9c847138735e039a3 to your computer and use it in GitHub Desktop.

Revisions

  1. Eideren revised this gist Oct 25, 2024. 1 changed file with 3 additions and 9 deletions.
    12 changes: 3 additions & 9 deletions elided.cs
    Original file line number Diff line number Diff line change
    @@ -32,16 +32,10 @@ public interface ICommonInterface
    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);
    @@ -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);
    batcher = (Elider)Activator.CreateInstance(gen)!;
    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
    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);
  2. Eideren created this gist Oct 25, 2024.
    12 changes: 12 additions & 0 deletions Benchmark.md
    Original 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.
    85 changes: 85 additions & 0 deletions elided.cs
    Original 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);
    }
    }
    }