Skip to content

Commit c831935

Browse files
committed
Update CSHARP-452 (Improve deserialization performance)
Robert and I had been discussing CHARP-4522 and we accidentally both implemented this separately. This merges the best parts from both of our work. (My original implementation can be found here optimiz3@f165220.) This change uses an O(1) bit array when determining which elements have been deserialized. One trick it uses is leveraging how .Net default initializes array values to 0 for the bit array. It also significantly enhances sparse structure processing. After running several tests I found this improved deserialization performance by an average 7% in the dense structure case and an average 27% in the sparse structure case over the current build in mainline.
1 parent 2ab98ce commit c831935

File tree

2 files changed

+118
-104
lines changed

2 files changed

+118
-104
lines changed

Bson/Serialization/BsonClassMap.cs

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18+
using System.Collections.ObjectModel;
1819
using System.IO;
1920
using System.Linq;
2021
using System.Linq.Expressions;
@@ -56,12 +57,14 @@ public abstract class BsonClassMap
5657
private bool _isRootClass;
5758
private bool _isAnonymous;
5859
private BsonMemberMap _idMemberMap;
59-
private List<BsonMemberMap> _allMemberMaps = new List<BsonMemberMap>(); // includes inherited member maps
60+
private readonly List<BsonMemberMap> _allMemberMaps; // includes inherited member maps
61+
private readonly ReadOnlyCollection<BsonMemberMap> _allMemberMapsReadonly;
6062
private List<BsonMemberMap> _declaredMemberMaps = new List<BsonMemberMap>(); // only the members declared in this class
61-
private Dictionary<string, BsonMemberMap> _elementDictionary = new Dictionary<string, BsonMemberMap>();
63+
private readonly Dictionary<string, int> _elementDictionary;
6264
private bool _ignoreExtraElements = true;
6365
private bool _ignoreExtraElementsIsInherited = false;
6466
private BsonMemberMap _extraElementsMemberMap;
67+
private int _extraElementsMemberIndex = -1;
6568
private List<Type> _knownTypes = new List<Type>();
6669

6770
// constructors
@@ -75,15 +78,18 @@ protected BsonClassMap(Type classType)
7578
_conventions = LookupConventions(classType);
7679
_discriminator = classType.Name;
7780
_isAnonymous = IsAnonymousType(classType);
81+
_allMemberMaps = new List<BsonMemberMap>();
82+
_allMemberMapsReadonly = _allMemberMaps.AsReadOnly();
83+
_elementDictionary = new Dictionary<string, int>();
7884
}
7985

8086
// public properties
8187
/// <summary>
8288
/// Gets all the member maps (including maps for inherited members).
8389
/// </summary>
84-
public IEnumerable<BsonMemberMap> AllMemberMaps
90+
public ReadOnlyCollection<BsonMemberMap> AllMemberMaps
8591
{
86-
get { return _allMemberMaps; }
92+
get { return _allMemberMapsReadonly; }
8793
}
8894

8995
/// <summary>
@@ -215,6 +221,15 @@ public IEnumerable<BsonMemberMap> MemberMaps
215221
get { return _allMemberMaps; }
216222
}
217223

224+
// internal properties
225+
/// <summary>
226+
/// Gets the member index of the member used to hold extra elements.
227+
/// </summary>
228+
internal int ExtraElementsMemberMapIndex
229+
{
230+
get { return _extraElementsMemberIndex; }
231+
}
232+
218233
// public static methods
219234
/// <summary>
220235
/// Gets the type of a member.
@@ -518,15 +533,18 @@ public BsonClassMap Freeze()
518533
}
519534
}
520535

521-
foreach (var memberMap in _allMemberMaps)
536+
_extraElementsMemberIndex = -1;
537+
for (int memberIndex = 0; memberIndex < _allMemberMaps.Count; ++memberIndex)
522538
{
523-
BsonMemberMap conflictingMemberMap;
524-
if (!_elementDictionary.TryGetValue(memberMap.ElementName, out conflictingMemberMap))
539+
var memberMap = _allMemberMaps[memberIndex];
540+
int conflictingMemberIndex;
541+
if (!_elementDictionary.TryGetValue(memberMap.ElementName, out conflictingMemberIndex))
525542
{
526-
_elementDictionary.Add(memberMap.ElementName, memberMap);
543+
_elementDictionary.Add(memberMap.ElementName, memberIndex);
527544
}
528545
else
529546
{
547+
var conflictingMemberMap = _allMemberMaps[conflictingMemberIndex];
530548
var fieldOrProperty = (memberMap.MemberInfo.MemberType == MemberTypes.Field) ? "field" : "property";
531549
var conflictingFieldOrProperty = (conflictingMemberMap.MemberInfo.MemberType == MemberTypes.Field) ? "field" : "property";
532550
var conflictingType = conflictingMemberMap.MemberInfo.DeclaringType;
@@ -546,6 +564,10 @@ public BsonClassMap Freeze()
546564
}
547565
throw new BsonSerializationException(message);
548566
}
567+
if (memberMap == _extraElementsMemberMap)
568+
{
569+
_extraElementsMemberIndex = memberIndex;
570+
}
549571
}
550572

551573
// mark this classMap frozen before we start working on knownTypes
@@ -611,8 +633,12 @@ public BsonMemberMap GetMemberMapForElement(string elementName)
611633
}
612634

613635
if (!_frozen) { ThrowNotFrozenException(); }
614-
BsonMemberMap memberMap;
615-
_elementDictionary.TryGetValue(elementName, out memberMap);
636+
int memberIndex;
637+
if (!_elementDictionary.TryGetValue(elementName, out memberIndex))
638+
{
639+
return null;
640+
}
641+
var memberMap = _allMemberMaps[memberIndex];
616642
return memberMap;
617643
}
618644

@@ -984,6 +1010,27 @@ internal IDiscriminatorConvention GetDiscriminatorConvention()
9841010
return discriminatorConvention;
9851011
}
9861012

1013+
/// <summary>
1014+
/// Gets the member map for a BSON element.
1015+
/// </summary>
1016+
/// <param name="elementName">The name of the element.</param>
1017+
/// <returns>The member map.</returns>
1018+
internal int GetMemberMapIndexForElement(string elementName)
1019+
{
1020+
if (elementName == null)
1021+
{
1022+
throw new ArgumentNullException("elementName");
1023+
}
1024+
1025+
if (!_frozen) { ThrowNotFrozenException(); }
1026+
int memberMapIndex;
1027+
if (!_elementDictionary.TryGetValue(elementName, out memberMapIndex))
1028+
{
1029+
return -1;
1030+
}
1031+
return memberMapIndex;
1032+
}
1033+
9871034
// private methods
9881035
private void AutoMapClass()
9891036
{

Bson/Serialization/BsonClassMapSerializer.cs

100755100644
Lines changed: 61 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ public object Deserialize(
135135
}
136136

137137
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
138-
var fastMemberMapFinder = new FastMemberMapFinder(_classMap);
138+
var allMemberMaps = _classMap.AllMemberMaps;
139+
var extraElementsMemberMapIndex = _classMap.ExtraElementsMemberMapIndex;
140+
var memberMapBitArray = FastMemberMapHelper.GetBitArray(allMemberMaps.Count);
139141

140142
bsonReader.ReadStartDocument();
141143
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
@@ -147,9 +149,10 @@ public object Deserialize(
147149
continue;
148150
}
149151

150-
var memberMap = fastMemberMapFinder.GetMemberMapForElement(elementName);
151-
if (memberMap != null)
152+
var memberMapIndex = _classMap.GetMemberMapIndexForElement(elementName);
153+
if (memberMapIndex >= 0 && memberMapIndex != extraElementsMemberMapIndex)
152154
{
155+
var memberMap = allMemberMaps[memberMapIndex];
153156
if (memberMap.IsReadOnly)
154157
{
155158
bsonReader.SkipValue();
@@ -158,12 +161,14 @@ public object Deserialize(
158161
{
159162
DeserializeMember(bsonReader, obj, memberMap);
160163
}
164+
memberMapBitArray[memberMapIndex >> 5] |= 1U << (memberMapIndex & 31);
161165
}
162166
else
163167
{
164-
if (_classMap.ExtraElementsMemberMap != null)
168+
if (extraElementsMemberMapIndex >= 0)
165169
{
166170
DeserializeExtraElement(bsonReader, obj, elementName, _classMap.ExtraElementsMemberMap);
171+
memberMapBitArray[extraElementsMemberMapIndex >> 5] |= 1U << (extraElementsMemberMapIndex & 31);
167172
}
168173
else if (_classMap.IgnoreExtraElements)
169174
{
@@ -181,25 +186,38 @@ public object Deserialize(
181186
bsonReader.ReadEndDocument();
182187

183188
// check any members left over that we didn't have elements for
184-
if (fastMemberMapFinder.HasLeftOverMemberMaps())
189+
for (var bitArrayIndex = 0; bitArrayIndex < memberMapBitArray.Length; ++bitArrayIndex)
185190
{
186-
foreach (var memberMap in fastMemberMapFinder.GetLeftOverMemberMaps())
191+
var memberMapIndex = bitArrayIndex << 5;
192+
var memberMapBlock = ~memberMapBitArray[bitArrayIndex];
193+
194+
for (;;)
187195
{
188-
if (memberMap.IsReadOnly)
196+
while ((memberMapBlock & 1) != 0)
189197
{
190-
continue;
198+
var memberMap = allMemberMaps[memberMapIndex];
199+
if (memberMap.IsRequired)
200+
{
201+
var fieldOrProperty = (memberMap.MemberInfo.MemberType == MemberTypes.Field) ? "field" : "property";
202+
var message = string.Format(
203+
"Required element '{0}' for {1} '{2}' of class {3} is missing.",
204+
memberMap.ElementName, fieldOrProperty, memberMap.MemberName, _classMap.ClassType.FullName);
205+
throw new FileFormatException(message);
206+
}
207+
memberMap.ApplyDefaultValue(obj);
208+
209+
++memberMapIndex;
210+
memberMapBlock >>= 1;
191211
}
192212

193-
if (memberMap.IsRequired)
213+
if (memberMapBlock == 0)
194214
{
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);
215+
break;
200216
}
201217

202-
memberMap.ApplyDefaultValue(obj);
218+
var leastSignificantBit = FastMemberMapHelper.GetLeastSignificantBit(memberMapBlock);
219+
memberMapIndex += leastSignificantBit;
220+
memberMapBlock >>= leastSignificantBit;
203221
}
204222
}
205223

@@ -359,18 +377,22 @@ public void Serialize(
359377
}
360378
}
361379

362-
foreach (var memberMap in _classMap.AllMemberMaps)
380+
var allMemberMaps = _classMap.AllMemberMaps;
381+
var extraElementsMemberMapIndex = _classMap.ExtraElementsMemberMapIndex;
382+
383+
for (var memberMapIndex = 0; memberMapIndex < allMemberMaps.Count; ++memberMapIndex)
363384
{
385+
var memberMap = allMemberMaps[memberMapIndex];
364386
// note: if serializeIdFirst is false then idMemberMap will be null (so no property will be skipped)
365387
if (memberMap != idMemberMap)
366388
{
367-
if (memberMap == _classMap.ExtraElementsMemberMap)
389+
if (memberMapIndex != extraElementsMemberMapIndex)
368390
{
369-
SerializeExtraElements(bsonWriter, value, memberMap);
391+
SerializeMember(bsonWriter, value, memberMap);
370392
}
371393
else
372394
{
373-
SerializeMember(bsonWriter, value, memberMap);
395+
SerializeExtraElements(bsonWriter, value, memberMap);
374396
}
375397
}
376398
}
@@ -531,90 +553,35 @@ private void VerifyNominalType(Type nominalType)
531553
}
532554

533555
// 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
556+
// helper class that implements member map bit array helper functions
557+
private static class FastMemberMapHelper
537558
{
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)
559+
private static readonly byte[] __multiplyDeBruijnBitIndex =
545560
{
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-
}
561+
0, 1, 28, 2, 29, 14, 24, 3,
562+
30, 22, 20, 15, 25, 17, 4, 8,
563+
31, 27, 13, 23, 21, 19, 16, 7,
564+
26, 12, 18, 6, 11, 5, 10, 9,
565+
};
572566

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()
567+
public static uint[] GetBitArray(int memberCount)
604568
{
605-
for (int i = _from; i <= _to; i++)
569+
var bitArrayOffset = memberCount & 31;
570+
var bitArrayLength = memberCount >> 5;
571+
if (bitArrayOffset == 0)
606572
{
607-
if (_memberMaps[i] != null)
608-
{
609-
return true;
610-
}
573+
return new uint[bitArrayLength];
611574
}
612-
return false;
575+
var bitArray = new uint[bitArrayLength + 1];
576+
bitArray[bitArrayLength] = ~0U << bitArrayOffset; // set unused bits to 1
577+
return bitArray;
613578
}
614579

615-
public IEnumerable<BsonMemberMap> GetLeftOverMemberMaps()
580+
// see http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup
581+
// also returns 0 if no bits are set; caller must check this case
582+
public static int GetLeastSignificantBit(uint bitBlock)
616583
{
617-
return _memberMaps.Where((m, i) => (i >= _from) && (i <= _to) && (m != null));
584+
return __multiplyDeBruijnBitIndex[((uint)((bitBlock & -bitBlock) * 0x077cb531U)) >> 27];
618585
}
619586
}
620587
}

0 commit comments

Comments
 (0)