Skip to content

Commit 2ab98ce

Browse files
author
Robert Stam
committed
Implemented CSHARP-452. Improved speed of BsonClassMapSerializer Deserialize by using a new FastMemberMapFinder helper class that doesn't use HashSet.
1 parent a82bd9a commit 2ab98ce

File tree

1 file changed

+137
-38
lines changed

1 file changed

+137
-38
lines changed

Bson/Serialization/BsonClassMapSerializer.cs

Lines changed: 137 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializ
7070
var serializer = BsonSerializer.LookupSerializer(actualType);
7171
if (serializer != this)
7272
{
73-
// in rare cases a concrete actualType might have a more specialized serializer
7473
return serializer.Deserialize(bsonReader, nominalType, actualType, options);
7574
}
7675
}
@@ -102,18 +101,24 @@ public object Deserialize(
102101
}
103102
else
104103
{
104+
if (actualType != _classMap.ClassType)
105+
{
106+
var message = string.Format("BsonClassMapSerializer.Deserialize for type {0} was called with actualType {1}.",
107+
BsonUtils.GetFriendlyTypeName(_classMap.ClassType), BsonUtils.GetFriendlyTypeName(actualType));
108+
throw new BsonSerializationException(message);
109+
}
110+
105111
if (actualType.IsValueType)
106112
{
107113
var message = string.Format("Value class {0} cannot be deserialized.", actualType.FullName);
108114
throw new BsonSerializationException(message);
109115
}
110116

111-
var classMap = BsonClassMap.LookupClassMap(actualType);
112-
if (classMap.IsAnonymous)
117+
if (_classMap.IsAnonymous)
113118
{
114119
throw new InvalidOperationException("An anonymous class cannot be deserialized.");
115120
}
116-
var obj = classMap.CreateInstance();
121+
var obj = _classMap.CreateInstance();
117122

118123
if (bsonType != BsonType.Document)
119124
{
@@ -129,9 +134,10 @@ public object Deserialize(
129134
supportsInitialization.BeginInit();
130135
}
131136

137+
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
138+
var fastMemberMapFinder = new FastMemberMapFinder(_classMap);
139+
132140
bsonReader.ReadStartDocument();
133-
var missingElementMemberMaps = new HashSet<BsonMemberMap>(classMap.AllMemberMaps); // make a copy!
134-
var discriminatorConvention = classMap.GetDiscriminatorConvention();
135141
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
136142
{
137143
var elementName = bsonReader.ReadName();
@@ -141,8 +147,8 @@ public object Deserialize(
141147
continue;
142148
}
143149

144-
var memberMap = classMap.GetMemberMapForElement(elementName);
145-
if (memberMap != null && memberMap != classMap.ExtraElementsMemberMap)
150+
var memberMap = fastMemberMapFinder.GetMemberMapForElement(elementName);
151+
if (memberMap != null)
146152
{
147153
if (memberMap.IsReadOnly)
148154
{
@@ -152,47 +158,49 @@ public object Deserialize(
152158
{
153159
DeserializeMember(bsonReader, obj, memberMap);
154160
}
155-
missingElementMemberMaps.Remove(memberMap);
156161
}
157162
else
158163
{
159-
if (classMap.ExtraElementsMemberMap != null)
164+
if (_classMap.ExtraElementsMemberMap != null)
160165
{
161-
DeserializeExtraElement(bsonReader, obj, elementName, classMap.ExtraElementsMemberMap);
162-
missingElementMemberMaps.Remove(classMap.ExtraElementsMemberMap);
166+
DeserializeExtraElement(bsonReader, obj, elementName, _classMap.ExtraElementsMemberMap);
163167
}
164-
else if (classMap.IgnoreExtraElements)
168+
else if (_classMap.IgnoreExtraElements)
165169
{
166170
bsonReader.SkipValue();
167171
}
168172
else
169173
{
170174
var message = string.Format(
171175
"Element '{0}' does not match any field or property of class {1}.",
172-
elementName, classMap.ClassType.FullName);
176+
elementName, _classMap.ClassType.FullName);
173177
throw new FileFormatException(message);
174178
}
175179
}
176180
}
177181
bsonReader.ReadEndDocument();
178182

179-
foreach (var memberMap in missingElementMemberMaps)
183+
// check any members left over that we didn't have elements for
184+
if (fastMemberMapFinder.HasLeftOverMemberMaps())
180185
{
181-
if (memberMap.IsReadOnly)
186+
foreach (var memberMap in fastMemberMapFinder.GetLeftOverMemberMaps())
182187
{
183-
continue;
184-
}
188+
if (memberMap.IsReadOnly)
189+
{
190+
continue;
191+
}
185192

186-
if (memberMap.IsRequired)
187-
{
188-
var fieldOrProperty = (memberMap.MemberInfo.MemberType == MemberTypes.Field) ? "field" : "property";
189-
var message = string.Format(
190-
"Required element '{0}' for {1} '{2}' of class {3} is missing.",
191-
memberMap.ElementName, fieldOrProperty, memberMap.MemberName, classMap.ClassType.FullName);
192-
throw new FileFormatException(message);
193-
}
193+
if (memberMap.IsRequired)
194+
{
195+
var fieldOrProperty = (memberMap.MemberInfo.MemberType == MemberTypes.Field) ? "field" : "property";
196+
var message = string.Format(
197+
"Required element '{0}' for {1} '{2}' of class {3} is missing.",
198+
memberMap.ElementName, fieldOrProperty, memberMap.MemberName, _classMap.ClassType.FullName);
199+
throw new FileFormatException(message);
200+
}
194201

195-
memberMap.ApplyDefaultValue(obj);
202+
memberMap.ApplyDefaultValue(obj);
203+
}
196204
}
197205

198206
if (supportsInitialization != null)
@@ -227,8 +235,7 @@ public bool GetDocumentId(
227235
out Type idNominalType,
228236
out IIdGenerator idGenerator)
229237
{
230-
var classMap = BsonClassMap.LookupClassMap(document.GetType());
231-
var idMemberMap = classMap.IdMemberMap;
238+
var idMemberMap = _classMap.IdMemberMap;
232239
if (idMemberMap != null)
233240
{
234241
id = idMemberMap.Getter(document);
@@ -309,7 +316,12 @@ public void Serialize(
309316

310317
VerifyNominalType(nominalType);
311318
var actualType = (value == null) ? nominalType : value.GetType();
312-
var classMap = BsonClassMap.LookupClassMap(actualType);
319+
if (actualType != _classMap.ClassType)
320+
{
321+
var message = string.Format("BsonClassMapSerializer.Serialize for type {0} was called with actualType {1}.",
322+
BsonUtils.GetFriendlyTypeName(_classMap.ClassType), BsonUtils.GetFriendlyTypeName(actualType));
323+
throw new BsonSerializationException(message);
324+
}
313325

314326
var documentSerializationOptions = (options ?? DocumentSerializationOptions.Defaults) as DocumentSerializationOptions;
315327
if (documentSerializationOptions == null)
@@ -325,19 +337,19 @@ public void Serialize(
325337
BsonMemberMap idMemberMap = null;
326338
if (documentSerializationOptions.SerializeIdFirst)
327339
{
328-
idMemberMap = classMap.IdMemberMap;
340+
idMemberMap = _classMap.IdMemberMap;
329341
if (idMemberMap != null)
330342
{
331343
SerializeMember(bsonWriter, value, idMemberMap);
332344
}
333345
}
334346

335-
if (actualType != nominalType || classMap.DiscriminatorIsRequired || classMap.HasRootClass)
347+
if (actualType != nominalType || _classMap.DiscriminatorIsRequired || _classMap.HasRootClass)
336348
{
337349
// never write out a discriminator for an anonymous class
338-
if (!classMap.IsAnonymous)
350+
if (!_classMap.IsAnonymous)
339351
{
340-
var discriminatorConvention = classMap.GetDiscriminatorConvention();
352+
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
341353
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
342354
if (discriminator != null)
343355
{
@@ -347,12 +359,12 @@ public void Serialize(
347359
}
348360
}
349361

350-
foreach (var memberMap in classMap.AllMemberMaps)
362+
foreach (var memberMap in _classMap.AllMemberMaps)
351363
{
352364
// note: if serializeIdFirst is false then idMemberMap will be null (so no property will be skipped)
353365
if (memberMap != idMemberMap)
354366
{
355-
if (memberMap == classMap.ExtraElementsMemberMap)
367+
if (memberMap == _classMap.ExtraElementsMemberMap)
356368
{
357369
SerializeExtraElements(bsonWriter, value, memberMap);
358370
}
@@ -380,8 +392,7 @@ public void SetDocumentId(object document, object id)
380392
throw new BsonSerializationException(message);
381393
}
382394

383-
var classMap = BsonClassMap.LookupClassMap(documentType);
384-
var idMemberMap = classMap.IdMemberMap;
395+
var idMemberMap = _classMap.IdMemberMap;
385396
if (idMemberMap != null)
386397
{
387398
idMemberMap.Setter(document, id);
@@ -518,5 +529,93 @@ private void VerifyNominalType(Type nominalType)
518529
throw new BsonSerializationException(message);
519530
}
520531
}
532+
533+
// nested classes
534+
// helper class that implements fast linear searching of member maps by using a shrinking range
535+
// and optimized for the case when the elements occur in the same order as the maps
536+
private class FastMemberMapFinder
537+
{
538+
private BsonClassMap _classMap;
539+
private BsonMemberMap _extraElementsMemberMap;
540+
private BsonMemberMap[] _memberMaps;
541+
private int _from;
542+
private int _to;
543+
544+
public FastMemberMapFinder(BsonClassMap classMap)
545+
{
546+
_classMap = classMap;
547+
_extraElementsMemberMap = classMap.ExtraElementsMemberMap;
548+
_memberMaps = classMap.AllMemberMaps.ToArray();
549+
_from = 0;
550+
_to = _memberMaps.Length - 1;
551+
if (_extraElementsMemberMap != null)
552+
{
553+
var i = Array.IndexOf(_memberMaps, _extraElementsMemberMap);
554+
_memberMaps[i] = null;
555+
}
556+
}
557+
558+
public BsonMemberMap GetMemberMapForElement(string elementName)
559+
{
560+
// linear search should be fast because elements will normally be in the same order as the member maps
561+
for (int i = _from; i <= _to; i++)
562+
{
563+
var memberMap = _memberMaps[i];
564+
if (memberMap == null)
565+
{
566+
if (i == _from)
567+
{
568+
_from++; // shrink the range from the left
569+
}
570+
continue;
571+
}
572+
573+
if (memberMap.ElementName == elementName)
574+
{
575+
if (i == _from)
576+
{
577+
_from++; // shrink the range from the left
578+
}
579+
else if (i == _to)
580+
{
581+
_to--; // shrink the range from the right
582+
}
583+
else
584+
{
585+
_memberMaps[i] = null; // set to null so we don't think it's missing
586+
}
587+
return memberMap;
588+
}
589+
}
590+
591+
// fall back to the class map in case it's a duplicate element name that we've already null'ed out
592+
// but make sure not to return the extraElementsMemberMap
593+
if (_extraElementsMemberMap == null || elementName != _extraElementsMemberMap.ElementName)
594+
{
595+
return _classMap.GetMemberMapForElement(elementName);
596+
}
597+
else
598+
{
599+
return null;
600+
}
601+
}
602+
603+
public bool HasLeftOverMemberMaps()
604+
{
605+
for (int i = _from; i <= _to; i++)
606+
{
607+
if (_memberMaps[i] != null)
608+
{
609+
return true;
610+
}
611+
}
612+
return false;
613+
}
614+
615+
public IEnumerable<BsonMemberMap> GetLeftOverMemberMaps()
616+
{
617+
return _memberMaps.Where((m, i) => (i >= _from) && (i <= _to) && (m != null));
618+
}
619+
}
521620
}
522621
}

0 commit comments

Comments
 (0)