Skip to content

Commit 288471f

Browse files
committed
Implement CSHARP-451 (Improve deserialization performance by ~22%).
Deserialization CPU performance was suffering because both LookupDiscriminatorConvention and LookupSerializer were being called for nearly every class member. Both functions take out and release the global config read-lock. Profiling showed that around 22% of all CPU time was being spent in ReaderWriterLockSlim.TryEnterReadLock and ReaderWriterLockSlim.ExitReadLock due to these functions. Fix makes the BsonClassMap and BsonMemberMap cache references to the appropriate IDiscriminatorConvention and IBsonSerializer instances. By doing this, lookups are avoided while maintaining semantic correctness due to the associated BsonClassMap and BsonMemberMap types never changing once constructed. Incorporated much feedback from discussion with Robert to simplify changes. Other issues addressed: 1. BsonSerializer.Serialize had a bug where a class implementing IBsonSerializable would always be serialized using the IBsonSerializable implemention, even if the implementation was overridden by a registered IBsonSerializer. 2. Modified BsonSerializer to allow only one custom serializer to be registered. This prevents cache consistency issues when a serializer instance is cached within a BsonMemberMap.
1 parent bf84f8d commit 288471f

File tree

4 files changed

+80
-30
lines changed

4 files changed

+80
-30
lines changed

Bson/Serialization/BsonClassMap.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public abstract class BsonClassMap
4747
private bool _frozen; // once a class map has been frozen no further changes are allowed
4848
private BsonClassMap _baseClassMap; // null for class object and interfaces
4949
private Type _classType;
50+
private volatile IDiscriminatorConvention _cachedDiscriminatorConvention;
5051
private Func<object> _creator;
5152
private ConventionProfile _conventions;
5253
private string _discriminator;
@@ -965,6 +966,22 @@ public void UnmapProperty(string propertyName)
965966
UnmapMember(propertyInfo);
966967
}
967968

969+
// internal methods
970+
/// <summary>
971+
/// Gets the discriminator convention for the member type.
972+
/// </summary>
973+
/// <returns>The discriminator convention for the member type.</returns>
974+
internal IDiscriminatorConvention GetDiscriminatorConvention()
975+
{
976+
var classDiscriminatorConvention = _cachedDiscriminatorConvention;
977+
if (classDiscriminatorConvention == null)
978+
{
979+
classDiscriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(_classType);
980+
_cachedDiscriminatorConvention = classDiscriminatorConvention;
981+
}
982+
return classDiscriminatorConvention;
983+
}
984+
968985
// private methods
969986
private void AutoMapClass()
970987
{
@@ -1485,7 +1502,7 @@ public void UnmapProperty<TMember>(Expression<Func<TClass, TMember>> propertyLam
14851502
UnmapMember(propertyLambda);
14861503
}
14871504

1488-
// private methods
1505+
// private static methods
14891506
private static MemberInfo GetMemberInfoFromLambda<TMember>(Expression<Func<TClass, TMember>> memberLambda)
14901507
{
14911508
var body = memberLambda.Body;

Bson/Serialization/BsonClassMapSerializer.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializ
6363
}
6464
else
6565
{
66-
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
66+
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
6767
var actualType = discriminatorConvention.GetActualType(bsonReader, nominalType);
6868
if (actualType != nominalType)
6969
{
@@ -131,7 +131,7 @@ public object Deserialize(
131131

132132
bsonReader.ReadStartDocument();
133133
var missingElementMemberMaps = new HashSet<BsonMemberMap>(classMap.AllMemberMaps); // make a copy!
134-
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
134+
var discriminatorConvention = classMap.GetDiscriminatorConvention();
135135
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
136136
{
137137
var elementName = bsonReader.ReadName();
@@ -337,7 +337,7 @@ public void Serialize(
337337
// never write out a discriminator for an anonymous class
338338
if (!classMap.IsAnonymous)
339339
{
340-
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
340+
var discriminatorConvention = classMap.GetDiscriminatorConvention();
341341
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
342342
if (discriminator != null)
343343
{
@@ -443,7 +443,7 @@ private void DeserializeMember(BsonReader bsonReader, object obj, BsonMemberMap
443443
}
444444
else
445445
{
446-
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
446+
var discriminatorConvention = memberMap.GetDiscriminatorConvention();
447447
actualType = discriminatorConvention.GetActualType(bsonReader, nominalType); // returns nominalType if no discriminator found
448448
}
449449
var serializer = memberMap.GetSerializer(actualType);

Bson/Serialization/BsonMemberMap.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public class BsonMemberMap
4444
private Action<object, object> _setter;
4545
private IBsonSerializationOptions _serializationOptions;
4646
private IBsonSerializer _serializer;
47+
private volatile IDiscriminatorConvention _cachedDiscriminatorConvention;
48+
private volatile IBsonSerializer _cachedSerializer;
4749
private IIdGenerator _idGenerator;
4850
private bool _isRequired;
4951
private Func<object, bool> _shouldSerializeMethod;
@@ -291,7 +293,20 @@ public IBsonSerializer GetSerializer(Type actualType)
291293
}
292294
else
293295
{
294-
return BsonSerializer.LookupSerializer(actualType);
296+
if (actualType == _memberType)
297+
{
298+
var cachedSerializer = _cachedSerializer;
299+
if (cachedSerializer == null)
300+
{
301+
cachedSerializer = BsonSerializer.LookupSerializer(_memberType);
302+
_cachedSerializer = cachedSerializer;
303+
}
304+
return cachedSerializer;
305+
}
306+
else
307+
{
308+
return BsonSerializer.LookupSerializer(actualType);
309+
}
295310
}
296311
}
297312

@@ -483,6 +498,22 @@ public bool ShouldSerialize(object obj, object value)
483498
return true;
484499
}
485500

501+
// internal methods
502+
/// <summary>
503+
/// Gets the discriminator convention for the member type.
504+
/// </summary>
505+
/// <returns>The discriminator convention for the member type.</returns>
506+
internal IDiscriminatorConvention GetDiscriminatorConvention()
507+
{
508+
var classDiscriminatorConvention = _cachedDiscriminatorConvention;
509+
if (classDiscriminatorConvention == null)
510+
{
511+
classDiscriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(_memberType);
512+
_cachedDiscriminatorConvention = classDiscriminatorConvention;
513+
}
514+
return classDiscriminatorConvention;
515+
}
516+
486517
// private methods
487518
private static object GetDefaultValue(Type type)
488519
{

Bson/Serialization/BsonSerializer.cs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ public static class BsonSerializer
4343
// static constructor
4444
static BsonSerializer()
4545
{
46-
RegisterDefaultSerializationProvider();
4746
RegisterIdGenerators();
4847
}
4948

@@ -359,7 +358,7 @@ public static IIdGenerator LookupIdGenerator(Type type)
359358
}
360359

361360
/// <summary>
362-
/// Looks up a serializer for a Type.
361+
/// Looks up and populates the serializer singleton for a type.
363362
/// </summary>
364363
/// <param name="type">The Type.</param>
365364
/// <returns>A serializer for the Type.</returns>
@@ -368,7 +367,7 @@ public static IBsonSerializer LookupSerializer(Type type)
368367
__configLock.EnterReadLock();
369368
try
370369
{
371-
IBsonSerializer serializer;
370+
IBsonSerializer serializer;
372371
if (__serializers.TryGetValue(type, out serializer))
373372
{
374373
return serializer;
@@ -385,6 +384,18 @@ public static IBsonSerializer LookupSerializer(Type type)
385384
IBsonSerializer serializer;
386385
if (!__serializers.TryGetValue(type, out serializer))
387386
{
387+
if (serializer == null)
388+
{
389+
foreach (var serializationProvider in __serializationProviders)
390+
{
391+
serializer = serializationProvider.GetSerializer(type);
392+
if (serializer != null)
393+
{
394+
break;
395+
}
396+
}
397+
}
398+
388399
// special case for IBsonSerializable
389400
if (serializer == null && typeof(IBsonSerializable).IsAssignableFrom(type))
390401
{
@@ -404,14 +415,7 @@ public static IBsonSerializer LookupSerializer(Type type)
404415

405416
if (serializer == null)
406417
{
407-
foreach (var serializationProvider in __serializationProviders)
408-
{
409-
serializer = serializationProvider.GetSerializer(type);
410-
if (serializer != null)
411-
{
412-
break;
413-
}
414-
}
418+
serializer = BsonDefaultSerializer.Instance.GetSerializer(type);
415419
}
416420

417421
if (serializer == null)
@@ -475,6 +479,11 @@ public static void RegisterIdGenerator(Type type, IIdGenerator idGenerator)
475479
/// <param name="provider">The serialization provider.</param>
476480
public static void RegisterSerializationProvider(IBsonSerializationProvider provider)
477481
{
482+
if (provider == BsonDefaultSerializer.Instance)
483+
{
484+
throw new ArgumentException("BsonDefaultSerializer is implicitly registered", "provider");
485+
}
486+
478487
__configLock.EnterWriteLock();
479488
try
480489
{
@@ -497,7 +506,12 @@ public static void RegisterSerializer(Type type, IBsonSerializer serializer)
497506
__configLock.EnterWriteLock();
498507
try
499508
{
500-
__serializers[type] = serializer;
509+
if (__serializers.ContainsKey(type))
510+
{
511+
var message = string.Format("There is already a serializer registered for type {0}.", type.FullName);
512+
throw new BsonSerializationException(message);
513+
}
514+
__serializers.Add(type, serializer);
501515
}
502516
finally
503517
{
@@ -555,24 +569,12 @@ public static void Serialize(
555569
object value,
556570
IBsonSerializationOptions options)
557571
{
558-
var bsonSerializable = value as IBsonSerializable;
559-
if (bsonSerializable != null)
560-
{
561-
bsonSerializable.Serialize(bsonWriter, nominalType, options);
562-
return;
563-
}
564-
565572
var actualType = (value == null) ? nominalType : value.GetType();
566573
var serializer = LookupSerializer(actualType);
567574
serializer.Serialize(bsonWriter, nominalType, value, options);
568575
}
569576

570577
// private static methods
571-
private static void RegisterDefaultSerializationProvider()
572-
{
573-
RegisterSerializationProvider(BsonDefaultSerializer.Instance);
574-
}
575-
576578
private static void RegisterIdGenerators()
577579
{
578580
BsonSerializer.RegisterIdGenerator(typeof(BsonObjectId), BsonObjectIdGenerator.Instance);

0 commit comments

Comments
 (0)