Last active
August 26, 2016 20:25
-
-
Save mattwarren/da62343df8fbdc5378df21e49df6a7c3 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
[Config(typeof(Config))] | |
public class LookingUpTypesWhenDeserializing | |
{ | |
static void Main(string[] args) | |
{ | |
var summary = BenchmarkRunner.Run<LookingUpTypesWhenDeserializing>(); | |
} | |
private class Config : ManualConfig | |
{ | |
public Config() | |
{ | |
Add(Job.Clr.WithLaunchCount(1).WithWarmupCount(5).WithTargetCount(5)); | |
Add(new MemoryDiagnoser()); | |
Add(JitOptimizationsValidator.FailOnError); | |
Add(RPlotExporter.Default); | |
} | |
} | |
// From https://rogeralsing.com/2016/08/16/wire-writing-one-of-the-fastest-net-serializers/ | |
// 1. Read the length of the type name | |
// 2. Read an UTF8 encoded byte array containing the type name | |
// 3. Translate the byte array to a string | |
// 4. Lookup the type with this name | |
// 5. Then finally lookup the value serializer for this type. | |
// | |
// To avoid both of these issues together, I introduced the idea of an ByteArrayKey, | |
// a struct that contains a byte array with a pre-computed hash code. | |
// This way, we can have a concurrent dictionary from ByteArrayKey to Type for fast lookups. | |
// So instead of doing step 3 and 4, I could simply take the byte array and lookup the type directly. | |
private static Type typeToUse = typeof(decimal); | |
private static byte[] encodedTypeName = Encoding.UTF8.GetBytes(typeToUse.Name); | |
private static ConcurrentDictionary<string, Type> StringToTypeLookup = | |
new ConcurrentDictionary<string, Type>( | |
new Dictionary<string, Type> | |
{ | |
{ typeof(int).Name, typeof(int) }, | |
{ typeof(uint).Name, typeof(uint) }, | |
{ typeof(long).Name, typeof(long) }, | |
{ typeof(double).Name, typeof(double) }, | |
{ typeof(float).Name, typeof(float) }, | |
{ typeof(decimal).Name, typeof(decimal) }, | |
{ typeof(DateTime).Name, typeof(DateTime) }, | |
{ typeof(Guid).Name, typeof(Guid) }, | |
}); | |
private static ConcurrentDictionary<ByteArrayKey, Type> ByteArrayKeyToTypeLookup = | |
new ConcurrentDictionary<ByteArrayKey, Type>( | |
new Dictionary<ByteArrayKey, Type> | |
{ | |
{ new ByteArrayKey(Encoding.UTF8.GetBytes(typeof(int).Name)), typeof(int) }, | |
{ new ByteArrayKey(Encoding.UTF8.GetBytes(typeof(uint).Name)), typeof(uint) }, | |
{ new ByteArrayKey(Encoding.UTF8.GetBytes(typeof(long).Name)), typeof(long) }, | |
{ new ByteArrayKey(Encoding.UTF8.GetBytes(typeof(double).Name)), typeof(double) }, | |
{ new ByteArrayKey(Encoding.UTF8.GetBytes(typeof(float).Name)), typeof(float) }, | |
{ new ByteArrayKey(Encoding.UTF8.GetBytes(typeof(decimal).Name)), typeof(decimal) }, | |
{ new ByteArrayKey(Encoding.UTF8.GetBytes(typeof(DateTime).Name)), typeof(DateTime) }, | |
{ new ByteArrayKey(Encoding.UTF8.GetBytes(typeof(Guid).Name)), typeof(Guid) }, | |
}); | |
// Same as ByteArrayKeyToTypeLookup, but just using a custom comparer (to prevent boxing on lookup) | |
private static ConcurrentDictionary<ByteArrayKey, Type> ByteArrayKeyToTypeLookupCustomComparer | |
= new ConcurrentDictionary<ByteArrayKey, Type>(ByteArrayKeyToTypeLookup, ByteArrayKeyComparer.Instance); | |
[Benchmark] | |
public Type TypeGetName() | |
{ | |
return Type.GetType(typeToUse.Name); | |
} | |
[Benchmark] | |
public Type SlowLookup() | |
{ | |
var name = Encoding.UTF8.GetString(encodedTypeName); | |
return StringToTypeLookup.GetOrAdd(name, newKey => null); | |
} | |
[Benchmark] | |
public Type OptimisedLookup() | |
{ | |
// Erm, why does this allocate?? | |
var key = ByteArrayKey.Create(encodedTypeName); | |
return ByteArrayKeyToTypeLookup.GetOrAdd(key, newKey => null); | |
} | |
[Benchmark] | |
public Type OptimisedLookupCustomComparer() | |
{ | |
var key = ByteArrayKey.Create(encodedTypeName); | |
return ByteArrayKeyToTypeLookupCustomComparer.GetOrAdd(key, newKey => null); | |
} | |
} | |
public class ByteArrayKeyComparer : IEqualityComparer<ByteArrayKey> | |
{ | |
public static readonly ByteArrayKeyComparer Instance = new ByteArrayKeyComparer(); | |
public bool Equals(ByteArrayKey x, ByteArrayKey y) | |
{ | |
return ByteArrayKey.Compare(x.Bytes, y.Bytes); | |
} | |
public int GetHashCode(ByteArrayKey obj) | |
{ | |
return obj.GetHashCode(); | |
} | |
} |
Host Process Environment Information:
BenchmarkDotNet.Core=v0.9.9.0
OS=Microsoft Windows NT 6.1.7601 Service Pack 1
Processor=Intel(R) Core(TM) i7-4800MQ CPU 2.70GHz, ProcessorCount=8
Frequency=2630761 ticks, Resolution=380.1181 ns, Timer=TSC
CLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE
GC=Concurrent Workstation
JitModules=clrjit-v4.6.1076.0
Type=LookingUpTypesWhenDeserializing Mode=Throughput Toolchain=Clr
Runtime=Clr LaunchCount=1 WarmupCount=5
TargetCount=5
Method | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
---|---|---|---|---|---|---|
TypeGetName | 3,771.3920 ns | 101.6729 ns | - | - | - | 9.64 |
SlowLookup | 84.6987 ns | 2.2779 ns | 7.52 | - | - | 10.22 |
OptimisedLookup | 36.7437 ns | 0.6846 ns | 3.61 | - | - | 4.94 |
OptimisedLookupCustomComparer | 30.5864 ns | 0.1341 ns | - | - | - | 0.00 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Results: