Skip to content

Instantly share code, notes, and snippets.

@mattwarren
Last active August 26, 2016 20:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattwarren/da62343df8fbdc5378df21e49df6a7c3 to your computer and use it in GitHub Desktop.
Save mattwarren/da62343df8fbdc5378df21e49df6a7c3 to your computer and use it in GitHub Desktop.
[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();
}
}
@mattwarren
Copy link
Author

Results:

image

@mattwarren
Copy link
Author

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