Skip to content

Commit 3477b3b

Browse files
KrzysFRabdullin
authored andcommitted
Re-optimize serialization of STuple<..> and ValueTuple<..> by having a cache of boxed serializers
1 parent 9e78d3d commit 3477b3b

File tree

2 files changed

+149
-140
lines changed

2 files changed

+149
-140
lines changed

FoundationDB.Client/Tuples/Encoding/TuplePacker.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public static void SerializeTo(ref TupleWriter writer, T value)
5757
Encoder(ref writer, value);
5858
}
5959

60+
public static void SerializeBoxedTo(ref TupleWriter writer, object value)
61+
{
62+
Encoder(ref writer, (T) value);
63+
}
64+
6065
/// <summary>Serialize a <typeparamref name="T"/> into a binary buffer</summary>
6166
/// <param name="writer">Target buffer</param>
6267
/// <param name="value">Value that will be serialized</param>

FoundationDB.Client/Tuples/Encoding/TuplePackers.cs

Lines changed: 144 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
2929
namespace Doxense.Collections.Tuples.Encoding
3030
{
3131
using System;
32+
using System.Collections.Concurrent;
3233
using System.Collections.Generic;
3334
using System.Globalization;
3435
using System.Linq;
@@ -76,8 +77,7 @@ private static Delegate GetSerializerFor([NotNull] Type type)
7677
}
7778

7879
// look for well-known types that have their own (non-generic) TuplePackers.SerializeTo(...) method
79-
var typeArgs = new[] { typeof(TupleWriter).MakeByRefType(), type };
80-
var method = typeof(TuplePackers).GetMethod(nameof(SerializeTo), BindingFlags.Static | BindingFlags.Public, binder: null, types: typeArgs, modifiers: null);
80+
var method = typeof(TuplePackers).GetMethod(nameof(SerializeTo), BindingFlags.Static | BindingFlags.Public, binder: null, types: new[] { typeof(TupleWriter).MakeByRefType(), type }, modifiers: null);
8181
if (method != null)
8282
{ // we have a direct serializer
8383
return method.CreateDelegate(typeof(Encoder<>).MakeGenericType(type));
@@ -97,7 +97,17 @@ private static Delegate GetSerializerFor([NotNull] Type type)
9797
// maybe it is a tuple ?
9898
if (typeof(ITuple).IsAssignableFrom(type))
9999
{
100-
// If so, try to use the corresponding TuplePackers.SerializeTupleTo(...) method
100+
if (type == typeof(STuple) || (type.Name.StartsWith(nameof(STuple) + "`", StringComparison.Ordinal) && type.Namespace == typeof(STuple).Namespace))
101+
{ // well-known STuple<T...> struct
102+
var typeArgs = type.GetGenericArguments();
103+
method = FindSTupleSerializerMethod(typeArgs);
104+
if (method != null)
105+
{
106+
return method.MakeGenericMethod(typeArgs).CreateDelegate(typeof(Encoder<>).MakeGenericType(type));
107+
}
108+
}
109+
110+
// will use the default ITuple implementation
101111
method = typeof(TuplePackers).GetMethod(nameof(SerializeTupleTo), BindingFlags.Static | BindingFlags.Public);
102112
if (method != null)
103113
{
@@ -116,9 +126,10 @@ private static Delegate GetSerializerFor([NotNull] Type type)
116126
}
117127
}
118128

119-
if ((type.Name == nameof(System.ValueTuple) || type.Name.StartsWith(nameof(System.ValueTuple) + "`", StringComparison.Ordinal)) && type.Namespace == "System")
129+
// ValueTuple<T..>
130+
if (type == typeof(ValueTuple) || (type.Name.StartsWith(nameof(System.ValueTuple) + "`", StringComparison.Ordinal) && type.Namespace == "System"))
120131
{
121-
typeArgs = type.GetGenericArguments();
132+
var typeArgs = type.GetGenericArguments();
122133
method = FindValueTupleSerializerMethod(typeArgs);
123134
if (method != null)
124135
{
@@ -132,6 +143,15 @@ private static Delegate GetSerializerFor([NotNull] Type type)
132143
return null;
133144
}
134145

146+
private static MethodInfo FindSTupleSerializerMethod(Type[] args)
147+
{
148+
//note: we want to find the correct SerializeSTuple<...>(ref TupleWriter, (...,), but this cannot be done with Type.GetMethod(...) directly
149+
// => we have to scan for all methods with the correct name, and the same number of Type Arguments than the ValueTuple.
150+
return typeof(TuplePackers)
151+
.GetMethods(BindingFlags.Static | BindingFlags.Public)
152+
.SingleOrDefault(m => m.Name == nameof(SerializeSTupleTo) && m.GetGenericArguments().Length == args.Length);
153+
}
154+
135155
private static MethodInfo FindValueTupleSerializerMethod(Type[] args)
136156
{
137157
//note: we want to find the correct SerializeValueTuple<...>(ref TupleWriter, (...,), but this cannot be done with Type.GetMethod(...) directly
@@ -220,149 +240,65 @@ public static void SerializeObjectTo(ref TupleWriter writer, object value)
220240
TupleParser.WriteNil(ref writer);
221241
return;
222242
}
243+
GetBoxedEncoder(value.GetType())(ref writer, value);
244+
}
223245

224-
switch (Type.GetTypeCode(value.GetType()))
246+
private static Encoder<object> GetBoxedEncoder(Type type)
247+
{
248+
if (!BoxedEncoders.TryGetValue(type, out var encoder))
225249
{
226-
case TypeCode.Empty:
227-
case TypeCode.Object:
228-
{
229-
if (value is byte[] bytes)
230-
{
231-
SerializeTo(ref writer, bytes);
232-
return;
233-
}
234-
235-
if (value is Slice slice)
236-
{
237-
SerializeTo(ref writer, slice);
238-
return;
239-
}
240-
241-
if (value is Guid g)
242-
{
243-
SerializeTo(ref writer, g);
244-
return;
245-
}
250+
encoder = CreateBoxedEncoder(type);
251+
BoxedEncoders.TryAdd(type, encoder);
252+
}
253+
return encoder;
254+
}
246255

247-
if (value is Uuid128 u128)
248-
{
249-
SerializeTo(ref writer, u128);
250-
return;
251-
}
256+
private static ConcurrentDictionary<Type, Encoder<object>> BoxedEncoders { get; } = GetDefaultBoxedEncoders();
252257

253-
if (value is Uuid64 u64)
254-
{
255-
SerializeTo(ref writer, u64);
256-
return;
257-
}
258-
259-
if (value is TimeSpan ts)
260-
{
261-
SerializeTo(ref writer, ts);
262-
return;
263-
}
258+
private static ConcurrentDictionary<Type, Encoder<object>> GetDefaultBoxedEncoders()
259+
{
260+
var encoders = new ConcurrentDictionary<Type, Encoder<object>>
261+
{
262+
[typeof(bool)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (bool) value),
263+
[typeof(char)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (char) value),
264+
[typeof(string)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (string) value),
265+
[typeof(sbyte)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (sbyte) value),
266+
[typeof(short)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (short) value),
267+
[typeof(int)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (int) value),
268+
[typeof(long)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (long) value),
269+
[typeof(byte)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (byte) value),
270+
[typeof(ushort)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (ushort) value),
271+
[typeof(uint)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (uint) value),
272+
[typeof(ulong)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (ulong) value),
273+
[typeof(float)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (float) value),
274+
[typeof(double)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (double) value),
275+
[typeof(decimal)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (decimal) value),
276+
[typeof(Slice)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (Slice) value),
277+
[typeof(byte[])] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (byte[]) value),
278+
[typeof(Guid)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (Guid) value),
279+
[typeof(Uuid128)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (Uuid128) value),
280+
[typeof(Uuid64)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (Uuid64) value),
281+
[typeof(TimeSpan)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (TimeSpan) value),
282+
[typeof(DateTime)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (DateTime) value),
283+
[typeof(DateTimeOffset)] = (ref TupleWriter writer, object value) => SerializeTo(ref writer, (DateTimeOffset) value),
284+
[typeof(ITuple)] = (ref TupleWriter writer, object value) => SerializeTupleTo(ref writer, (ITuple) value),
285+
[typeof(ITupleFormattable)] = (ref TupleWriter writer, object value) => SerializeTupleTo(ref writer, (ITuple) value),
286+
[typeof(DBNull)] = (ref TupleWriter writer, object value) => TupleParser.WriteNil(ref writer)
287+
};
264288

265-
break;
266-
}
267-
case TypeCode.DBNull:
268-
{ // same as null
269-
TupleParser.WriteNil(ref writer);
270-
return;
271-
}
272-
case TypeCode.Boolean:
273-
{
274-
SerializeTo(ref writer, (bool)value);
275-
return;
276-
}
277-
case TypeCode.Char:
278-
{
279-
// should be treated as a string with only one char
280-
SerializeTo(ref writer, (char)value);
281-
return;
282-
}
283-
case TypeCode.SByte:
284-
{
285-
SerializeTo(ref writer, (sbyte)value);
286-
return;
287-
}
288-
case TypeCode.Byte:
289-
{
290-
SerializeTo(ref writer, (byte)value);
291-
return;
292-
}
293-
case TypeCode.Int16:
294-
{
295-
SerializeTo(ref writer, (short)value);
296-
return;
297-
}
298-
case TypeCode.UInt16:
299-
{
300-
SerializeTo(ref writer, (ushort)value);
301-
return;
302-
}
303-
case TypeCode.Int32:
304-
{
305-
SerializeTo(ref writer, (int)value);
306-
return;
307-
}
308-
case TypeCode.UInt32:
309-
{
310-
SerializeTo(ref writer, (uint)value);
311-
return;
312-
}
313-
case TypeCode.Int64:
314-
{
315-
SerializeTo(ref writer, (long)value);
316-
return;
317-
}
318-
case TypeCode.UInt64:
319-
{
320-
SerializeTo(ref writer, (ulong)value);
321-
return;
322-
}
323-
case TypeCode.String:
324-
{
325-
SerializeTo(ref writer, value as string);
326-
return;
327-
}
328-
case TypeCode.DateTime:
329-
{
330-
SerializeTo(ref writer, (DateTime)value);
331-
return;
332-
}
333-
case TypeCode.Double:
334-
{
335-
SerializeTo(ref writer, (double)value);
336-
return;
337-
}
338-
case TypeCode.Single:
339-
{
340-
SerializeTo(ref writer, (float)value);
341-
return;
342-
}
343-
case TypeCode.Decimal:
344-
{
345-
SerializeTo(ref writer, (decimal)value);
346-
return;
347-
}
348-
}
289+
return encoders;
290+
}
349291

350-
if (value is ITuple tuple)
351-
{
352-
SerializeTupleTo(ref writer, tuple);
353-
return;
354-
}
292+
private static Encoder<object> CreateBoxedEncoder(Type type)
293+
{
294+
var m = typeof(TuplePacker<>).MakeGenericType(type).GetMethod(nameof(TuplePacker<int>.SerializeBoxedTo));
295+
Contract.Assert(m != null);
355296

356-
if (value is ITupleFormattable fmt)
357-
{
358-
tuple = fmt.ToTuple();
359-
if (tuple == null) throw new InvalidOperationException($"An instance of type '{value.GetType().Name}' returned a null Tuple while serialiazing");
360-
SerializeTupleTo(ref writer, tuple);
361-
return;
362-
}
297+
var writer = Expression.Parameter(typeof(TupleWriter).MakeByRefType(), "writer");
298+
var value = Expression.Parameter(typeof(object), "value");
363299

364-
// Not Supported ?
365-
throw new NotSupportedException($"Doesn't know how to serialize objects of type '{value.GetType().Name}' into Tuple Encoding format");
300+
var body = Expression.Call(m, writer, value);
301+
return Expression.Lambda<Encoder<object>>(body, writer, value).Compile();
366302
}
367303

368304
/// <summary>Writes a slice as a byte[] array</summary>
@@ -570,6 +506,74 @@ public static void SerializeTupleTo<TTuple>(ref TupleWriter writer, TTuple tuple
570506
TupleParser.EndTuple(ref writer);
571507
}
572508

509+
public static void SerializeSTupleTo<T1>(ref TupleWriter writer, STuple<T1> tuple)
510+
{
511+
TupleParser.BeginTuple(ref writer);
512+
SerializeTo(ref writer, tuple.Item1);
513+
TupleParser.EndTuple(ref writer);
514+
}
515+
516+
public static void SerializeSTupleTo<T1, T2>(ref TupleWriter writer, STuple<T1, T2> tuple)
517+
{
518+
TupleParser.BeginTuple(ref writer);
519+
SerializeTo(ref writer, tuple.Item1);
520+
SerializeTo(ref writer, tuple.Item2);
521+
TupleParser.EndTuple(ref writer);
522+
}
523+
524+
public static void SerializeSTupleTo<T1, T2, T3>(ref TupleWriter writer, STuple<T1, T2, T3> tuple)
525+
{
526+
TupleParser.BeginTuple(ref writer);
527+
SerializeTo(ref writer, tuple.Item1);
528+
SerializeTo(ref writer, tuple.Item2);
529+
SerializeTo(ref writer, tuple.Item3);
530+
TupleParser.EndTuple(ref writer);
531+
}
532+
533+
public static void SerializeSTupleTo<T1, T2, T3, T4>(ref TupleWriter writer, STuple<T1, T2, T3, T4> tuple)
534+
{
535+
TupleParser.BeginTuple(ref writer);
536+
SerializeTo(ref writer, tuple.Item1);
537+
SerializeTo(ref writer, tuple.Item2);
538+
SerializeTo(ref writer, tuple.Item3);
539+
SerializeTo(ref writer, tuple.Item4);
540+
TupleParser.EndTuple(ref writer);
541+
}
542+
543+
public static void SerializeSTupleTo<T1, T2, T3, T4, T5>(ref TupleWriter writer, STuple<T1, T2, T3, T4, T5> tuple)
544+
{
545+
TupleParser.BeginTuple(ref writer);
546+
SerializeTo(ref writer, tuple.Item1);
547+
SerializeTo(ref writer, tuple.Item2);
548+
SerializeTo(ref writer, tuple.Item3);
549+
SerializeTo(ref writer, tuple.Item4);
550+
SerializeTo(ref writer, tuple.Item5);
551+
TupleParser.EndTuple(ref writer);
552+
}
553+
554+
public static void SerializeSTupleTo<T1, T2, T3, T4, T5, T6>(ref TupleWriter writer, STuple<T1, T2, T3, T4, T5, T6> tuple)
555+
{
556+
TupleParser.BeginTuple(ref writer);
557+
SerializeTo(ref writer, tuple.Item1);
558+
SerializeTo(ref writer, tuple.Item2);
559+
SerializeTo(ref writer, tuple.Item3);
560+
SerializeTo(ref writer, tuple.Item4);
561+
SerializeTo(ref writer, tuple.Item5);
562+
SerializeTo(ref writer, tuple.Item6);
563+
TupleParser.EndTuple(ref writer);
564+
}
565+
566+
public static void SerializeTupleFormattableTo<TFormattable>(ref TupleWriter writer, TFormattable formattable)
567+
where TFormattable : ITupleFormattable
568+
{
569+
var tuple = formattable.ToTuple();
570+
if (tuple == null) throw new InvalidOperationException($"An instance of type '{formattable.GetType().Name}' returned a null Tuple while serialiazing");
571+
572+
TupleParser.BeginTuple(ref writer);
573+
TupleEncoder.WriteTo(ref writer, tuple);
574+
TupleParser.EndTuple(ref writer);
575+
}
576+
573577
/// <summary>Serialize an embedded tuple formattable</summary>
574578
public static void SerializeFormattableTo(ref TupleWriter writer, ITupleFormattable formattable)
575579
{

0 commit comments

Comments
 (0)