Created
March 7, 2010 23:48
-
-
Save bgrainger/324732 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
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