Skip to content

Instantly share code, notes, and snippets.

@bgrainger
Created March 7, 2010 23:48
Show Gist options
  • Save bgrainger/324732 to your computer and use it in GitHub Desktop.
Save bgrainger/324732 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace DynamicDispatch
{
interface IAnimalVisitor
{
void Visit(Bear bear);
void Visit(Lion lion);
void Visit(Snake snake);
void Visit(Tiger tiger);
}
abstract class Animal
{
public abstract void Accept(IAnimalVisitor visitor);
}
abstract class Mammal : Animal { }
abstract class Feline : Mammal { }
sealed class Lion : Feline
{
public override void Accept(IAnimalVisitor visitor) { visitor.Visit(this); }
}
sealed class Tiger : Feline
{
public override void Accept(IAnimalVisitor visitor) { visitor.Visit(this); }
}
sealed class Bear : Mammal
{
public override void Accept(IAnimalVisitor visitor) { visitor.Visit(this); }
}
abstract class Reptile : Animal { }
sealed class Snake : Reptile
{
public override void Accept(IAnimalVisitor visitor) { visitor.Visit(this); }
}
class Program
{
static void Main(string[] args)
{
// set up input collections
var animals = new List<Animal>
{
new Lion(), new Lion(), new Tiger(), new Tiger(),
new Bear(), new Bear(), new Snake(), new Lion(),
new Tiger(), new Bear(),
};
var mammals = new List<Animal>(Enumerable.Range(0, 9).Select(_ => new Lion()));
mammals.Add(new Tiger());
var lions = new List<Animal>(Enumerable.Range(0, 10).Select(_ => new Lion()));
InitializeMethods(animals);
int reps = 1000000;
var tests = new[] {
Tuple.Create("animals", animals),
Tuple.Create("mammals", mammals),
Tuple.Create("lions", lions),
};
long[] timings = new long[7];
int passes = 10;
for (int pass = 0; pass < passes; pass++)
foreach (var test in tests.Select(t => new { Name = t.Item1, Collection = t.Item2 }))
{
// NOTE: All tests are hard-coded to avoid timing errors that could potentially be introduced by calling
// through an interface, delegate, base class etc.
var classicVisitor = new ClassicVisitor();
var sw = Stopwatch.StartNew();
for (int rep = 0; rep < reps; rep++)
classicVisitor.VisitAll(test.Collection);
sw.Stop();
Console.WriteLine("Classic took {0} to find {1:n0} {2}.",
sw.Elapsed, classicVisitor.Lions + classicVisitor.Tigers + classicVisitor.Bears + classicVisitor.Snakes, test.Name);
timings[0] += sw.ElapsedMilliseconds;
var dynamicVisitor = new DynamicVisitor();
sw = Stopwatch.StartNew();
for (int rep = 0; rep < reps; rep++)
dynamicVisitor.VisitAll(test.Collection);
sw.Stop();
Console.WriteLine("Dynamic took {0} to find {1:n0} {2}.",
sw.Elapsed, dynamicVisitor.Lions + dynamicVisitor.Tigers + dynamicVisitor.Bears + dynamicVisitor.Snakes, test.Name);
timings[1] += sw.ElapsedMilliseconds;
var dynamicVisitor2 = new DynamicVisitor2();
sw = Stopwatch.StartNew();
for (int rep = 0; rep < reps; rep++)
dynamicVisitor2.VisitAll(test.Collection);
sw.Stop();
Console.WriteLine("Dynamc2 took {0} to find {1:n0} {2}.",
sw.Elapsed, dynamicVisitor2.Mammals + dynamicVisitor2.Reptiles, test.Name);
timings[2] += sw.ElapsedMilliseconds;
var reflectionVisitor = new ReflectionVisitor();
sw = Stopwatch.StartNew();
for (int rep = 0; rep < reps; rep++)
reflectionVisitor.VisitAll(test.Collection);
sw.Stop();
Console.WriteLine("Reflect took {0} to find {1:n0} {2}.",
sw.Elapsed, reflectionVisitor.Lions + reflectionVisitor.Tigers + reflectionVisitor.Bears + reflectionVisitor.Snakes, test.Name);
timings[3] += sw.ElapsedMilliseconds;
var typeCheckingVisitor = new TypeCheckingVisitor();
sw = Stopwatch.StartNew();
for (int rep = 0; rep < reps; rep++)
typeCheckingVisitor.VisitAll(test.Collection);
sw.Stop();
Console.WriteLine("TypeChk took {0} to find {1:n0} {2}.",
sw.Elapsed, typeCheckingVisitor.Lions + typeCheckingVisitor.Tigers + typeCheckingVisitor.Bears + typeCheckingVisitor.Snakes, test.Name);
timings[4] += sw.ElapsedMilliseconds;
var delegateVisitor = new DelegateVisitor();
sw = Stopwatch.StartNew();
for (int rep = 0; rep < reps; rep++)
delegateVisitor.VisitAll(test.Collection);
sw.Stop();
Console.WriteLine("Delegat took {0} to find {1:n0} {2}.",
sw.Elapsed, delegateVisitor.Lions + delegateVisitor.Tigers + delegateVisitor.Bears + delegateVisitor.Snakes, test.Name);
timings[5] += sw.ElapsedMilliseconds;
var nullVisitor = new NullVisitor();
sw = Stopwatch.StartNew();
for (int rep = 0; rep < reps; rep++)
nullVisitor.VisitAll(test.Collection);
sw.Stop();
Console.WriteLine("Null took {0} to find {1:n0} {2}.",
sw.Elapsed, nullVisitor.Animals, test.Name);
timings[6] += sw.ElapsedMilliseconds;
}
Console.WriteLine("Classic: {0}ms", timings[0] / passes);
Console.WriteLine("Dynamic: {0}ms", timings[1] / passes);
Console.WriteLine("Dynamc2: {0}ms", timings[2] / passes);
Console.WriteLine("Reflect: {0}ms", timings[3] / passes);
Console.WriteLine("TypeChk: {0}ms", timings[4] / passes);
Console.WriteLine("Delegat: {0}ms", timings[5] / passes);
Console.WriteLine("Null : {0}ms", timings[6] / passes);
}
// Calls the dynamic methods once to incur startup costs outside of the loop.
static void InitializeMethods(IEnumerable<Animal> animals)
{
DynamicVisitor dynamicVisitor = new DynamicVisitor();
Stopwatch sw = Stopwatch.StartNew();
dynamicVisitor.VisitAll(animals);
sw.Stop();
TimeSpan ts = sw.Elapsed;
sw = Stopwatch.StartNew();
dynamicVisitor.VisitAll(animals);
sw.Stop();
DynamicVisitor2 dynamicVisitor2 = new DynamicVisitor2();
dynamicVisitor2.VisitAll(animals);
Console.WriteLine("Dynamic took {0} for first visit, and {1} for second.", ts, sw.Elapsed);
ReflectionVisitor reflectionVisitor = new ReflectionVisitor();
sw = Stopwatch.StartNew();
reflectionVisitor.VisitAll(animals);
sw.Stop();
ts = sw.Elapsed;
sw = Stopwatch.StartNew();
reflectionVisitor.VisitAll(animals);
sw.Stop();
Console.WriteLine("Reflect took {0} for first visit, and {1} for second.", ts, sw.Elapsed);
}
}
class ClassicVisitor : IAnimalVisitor
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void VisitAll(IEnumerable<Animal> animals)
{
foreach (Animal animal in animals)
animal.Accept(this);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void Visit(Bear bear)
{
Bears++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void Visit(Lion lion)
{
Lions++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void Visit(Snake snake)
{
Snakes++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void Visit(Tiger tiger)
{
Tigers++;
}
public int Lions { get; private set; }
public int Tigers { get; private set; }
public int Bears { get; private set; }
public int Snakes { get; private set; }
}
class DynamicVisitor
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void VisitAll(IEnumerable<Animal> animals)
{
foreach (Animal animal in animals)
Visit((dynamic) animal);
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Bear bear)
{
Bears++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Lion lion)
{
Lions++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Snake snake)
{
Snakes++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Tiger tiger)
{
Tigers++;
}
public int Lions { get; private set; }
public int Tigers { get; private set; }
public int Bears { get; private set; }
public int Snakes { get; private set; }
}
class DynamicVisitor2
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void VisitAll(IEnumerable<Animal> animals)
{
foreach (Animal animal in animals)
Visit((dynamic) animal);
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Mammal mammal)
{
Mammals++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Reptile reptile)
{
Reptiles++;
}
public int Mammals { get; private set; }
public int Reptiles { get; private set; }
}
class TypeCheckingVisitor
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void VisitAll(IEnumerable<Animal> animals)
{
foreach (Animal animal in animals)
{
Bear bear = animal as Bear;
if (bear != null)
{
Visit(bear);
continue;
}
Lion lion = animal as Lion;
if (lion != null)
{
Visit(lion);
continue;
}
Snake snake = animal as Snake;
if (snake != null)
{
Visit(snake);
continue;
}
Tiger tiger = animal as Tiger;
if (tiger != null)
{
Visit(tiger);
continue;
}
throw new InvalidOperationException();
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Bear bear)
{
Bears++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Lion lion)
{
Lions++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Snake snake)
{
Snakes++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Tiger tiger)
{
Tigers++;
}
public int Lions { get; private set; }
public int Tigers { get; private set; }
public int Bears { get; private set; }
public int Snakes { get; private set; }
}
class ReflectionVisitor
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void VisitAll(IEnumerable<Animal> animals)
{
foreach (Animal animal in animals)
{
Type type = animal.GetType();
MethodInfo mi;
if (!s_methods.TryGetValue(type, out mi))
{
mi =
(from m in GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
where m.Name == "Visit"
let p = m.GetParameters()[0]
where p.ParameterType == type
select m).Single();
s_methods.Add(type, mi);
}
mi.Invoke(this, new object[] { animal });
}
}
void Visit(Bear bear)
{
Bears++;
}
void Visit(Lion lion)
{
Lions++;
}
void Visit(Snake snake)
{
Snakes++;
}
void Visit(Tiger tiger)
{
Tigers++;
}
public int Lions { get; private set; }
public int Tigers { get; private set; }
public int Bears { get; private set; }
public int Snakes { get; private set; }
static readonly Dictionary<Type, MethodInfo> s_methods = new Dictionary<Type, MethodInfo>();
}
class DelegateVisitor
{
public DelegateVisitor()
{
m_methods = new Dictionary<Type, Action<Animal>>
{
{ typeof(Bear), a => Visit((Bear) a) },
{ typeof(Lion), a => Visit((Lion) a) },
{ typeof(Snake), a => Visit((Snake) a) },
{ typeof(Tiger), a => Visit((Tiger) a) },
};
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void VisitAll(IEnumerable<Animal> animals)
{
foreach (Animal animal in animals)
m_methods[animal.GetType()](animal);
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Bear bear)
{
Bears++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Lion lion)
{
Lions++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Snake snake)
{
Snakes++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
void Visit(Tiger tiger)
{
Tigers++;
}
public int Lions { get; private set; }
public int Tigers { get; private set; }
public int Bears { get; private set; }
public int Snakes { get; private set; }
readonly Dictionary<Type, Action<Animal>> m_methods;
}
class NullVisitor
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void VisitAll(IEnumerable<Animal> animals)
{
foreach (Animal animal in animals)
Animals++;
}
public int Animals { get; private set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment