Skip to content

Commit a7ff59a

Browse files
committed
CSHARP-5666: Remove GetBitArray allocations in BsonClassMapSerializer.DeserializeClass
1 parent c4cc63b commit a7ff59a

File tree

2 files changed

+282
-76
lines changed

2 files changed

+282
-76
lines changed

src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs

Lines changed: 91 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
*/
1515

1616
using System;
17+
using System.Buffers;
1718
using System.Collections.Generic;
1819
using System.ComponentModel;
1920
using System.Reflection;
21+
using System.Runtime.CompilerServices;
2022
using MongoDB.Bson.IO;
2123
using MongoDB.Bson.Serialization.Conventions;
2224
using MongoDB.Bson.Serialization.Serializers;
@@ -82,7 +84,7 @@ public override TClass Deserialize(BsonDeserializationContext context, BsonDeser
8284
{
8385
var bsonReader = context.Reader;
8486

85-
if (bsonReader.GetCurrentBsonType() == Bson.BsonType.Null)
87+
if (bsonReader.GetCurrentBsonType() == BsonType.Null)
8688
{
8789
bsonReader.ReadNull();
8890
return default(TClass);
@@ -149,7 +151,9 @@ public TClass DeserializeClass(BsonDeserializationContext context)
149151
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
150152
var allMemberMaps = _classMap.AllMemberMaps;
151153
var extraElementsMemberMapIndex = _classMap.ExtraElementsMemberMapIndex;
152-
var memberMapBitArray = FastMemberMapHelper.GetBitArray(allMemberMaps.Count);
154+
155+
var (bitArrayLength, useStackAlloc) = FastMemberMapHelper.GetBitArrayLength(_classMap.AllMemberMaps.Count);
156+
using var bitArray = useStackAlloc ? FastMemberMapHelper.GetBitArray(stackalloc uint[bitArrayLength]) : FastMemberMapHelper.GetBitArray(bitArrayLength);
153157

154158
bsonReader.ReadStartDocument();
155159
var elementTrie = _classMap.ElementTrie;
@@ -193,7 +197,8 @@ public TClass DeserializeClass(BsonDeserializationContext context)
193197
DeserializeExtraElementValue(context, values, elementName, memberMap);
194198
}
195199
}
196-
memberMapBitArray[memberMapIndex >> 5] |= 1U << (memberMapIndex & 31);
200+
201+
bitArray.SetMemberIndex(memberMapIndex);
197202
}
198203
else
199204
{
@@ -221,7 +226,7 @@ public TClass DeserializeClass(BsonDeserializationContext context)
221226
{
222227
DeserializeExtraElementValue(context, values, elementName, extraElementsMemberMap);
223228
}
224-
memberMapBitArray[extraElementsMemberMapIndex >> 5] |= 1U << (extraElementsMemberMapIndex & 31);
229+
bitArray.SetMemberIndex(extraElementsMemberMapIndex);
225230
}
226231
else if (_classMap.IgnoreExtraElements)
227232
{
@@ -239,51 +244,38 @@ public TClass DeserializeClass(BsonDeserializationContext context)
239244
bsonReader.ReadEndDocument();
240245

241246
// check any members left over that we didn't have elements for (in blocks of 32 elements at a time)
242-
for (var bitArrayIndex = 0; bitArrayIndex < memberMapBitArray.Length; ++bitArrayIndex)
247+
var bitArraySpan = bitArray.Span;
248+
for (var bitArrayIndex = 0; bitArrayIndex < bitArraySpan.Length; bitArrayIndex++)
243249
{
244250
var memberMapIndex = bitArrayIndex << 5;
245-
var memberMapBlock = ~memberMapBitArray[bitArrayIndex]; // notice that bits are flipped so 1's are now the missing elements
251+
var memberMapBlock = ~bitArraySpan[bitArrayIndex]; // notice that bits are flipped so 1's are now the missing elements
246252

247253
// work through this memberMapBlock of 32 elements
248-
while (true)
254+
for (; memberMapBlock != 0 && memberMapIndex < allMemberMaps.Count; memberMapIndex++, memberMapBlock >>= 1)
249255
{
250-
// examine missing elements (memberMapBlock is shifted right as we work through the block)
251-
for (; (memberMapBlock & 1) != 0; ++memberMapIndex, memberMapBlock >>= 1)
252-
{
253-
var memberMap = allMemberMaps[memberMapIndex];
254-
if (memberMap.IsReadOnly)
255-
{
256-
continue;
257-
}
258-
259-
if (memberMap.IsRequired)
260-
{
261-
var fieldOrProperty = (memberMap.MemberInfo is FieldInfo) ? "field" : "property";
262-
var message = string.Format(
263-
"Required element '{0}' for {1} '{2}' of class {3} is missing.",
264-
memberMap.ElementName, fieldOrProperty, memberMap.MemberName, _classMap.ClassType.FullName);
265-
throw new FormatException(message);
266-
}
256+
if ((memberMapBlock & 1) == 0)
257+
continue;
267258

268-
if (document != null)
269-
{
270-
memberMap.ApplyDefaultValue(document);
271-
}
272-
else if (memberMap.IsDefaultValueSpecified && !memberMap.IsReadOnly)
273-
{
274-
values[memberMap.ElementName] = memberMap.DefaultValue;
275-
}
259+
var memberMap = allMemberMaps[memberMapIndex];
260+
if (memberMap.IsReadOnly)
261+
{
262+
continue;
276263
}
277264

278-
if (memberMapBlock == 0)
265+
if (memberMap.IsRequired)
279266
{
280-
break;
267+
var fieldOrProperty = (memberMap.MemberInfo is FieldInfo) ? "field" : "property";
268+
throw new FormatException($"Required element 'memberMap.ElementName' for {fieldOrProperty} '{memberMap.MemberName}' of class {_classMap.ClassType.FullName} is missing.");
281269
}
282270

283-
// skip ahead to the next missing element
284-
var leastSignificantBit = FastMemberMapHelper.GetLeastSignificantBit(memberMapBlock);
285-
memberMapIndex += leastSignificantBit;
286-
memberMapBlock >>= leastSignificantBit;
271+
if (document != null)
272+
{
273+
memberMap.ApplyDefaultValue(document);
274+
}
275+
else if (memberMap.IsDefaultValueSpecified && !memberMap.IsReadOnly)
276+
{
277+
values[memberMap.ElementName] = memberMap.DefaultValue;
278+
}
287279
}
288280
}
289281

@@ -335,13 +327,11 @@ public bool GetDocumentId(
335327
idGenerator = idMemberMap.IdGenerator;
336328
return true;
337329
}
338-
else
339-
{
340-
id = null;
341-
idNominalType = null;
342-
idGenerator = null;
343-
return false;
344-
}
330+
331+
id = null;
332+
idNominalType = null;
333+
idGenerator = null;
334+
return false;
345335
}
346336

347337
/// <summary>
@@ -694,47 +684,73 @@ private bool ShouldSerializeDiscriminator(Type nominalType)
694684

695685
// nested classes
696686
// helper class that implements member map bit array helper functions
697-
private static class FastMemberMapHelper
687+
internal static class FastMemberMapHelper
698688
{
699-
public static uint[] GetBitArray(int memberCount)
689+
internal ref struct BitArray()
700690
{
701-
var bitArrayOffset = memberCount & 31;
702-
var bitArrayLength = memberCount >> 5;
703-
if (bitArrayOffset == 0)
704-
{
705-
return new uint[bitArrayLength];
706-
}
707-
var bitArray = new uint[bitArrayLength + 1];
708-
bitArray[bitArrayLength] = ~0U << bitArrayOffset; // set unused bits to 1
709-
return bitArray;
710-
}
691+
private readonly ArrayPool<uint> _arrayPool;
692+
private readonly Span<uint> _bitArray;
693+
private readonly uint[] _rentedBuffer;
694+
private bool _isDisposed = false;
711695

712-
// see http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightBinSearch
713-
// also returns 31 if no bits are set; caller must check this case
714-
public static int GetLeastSignificantBit(uint bitBlock)
715-
{
716-
var leastSignificantBit = 1;
717-
if ((bitBlock & 65535) == 0)
696+
public BitArray(Span<uint> bitArray) : this()
718697
{
719-
bitBlock >>= 16;
720-
leastSignificantBit |= 16;
698+
_arrayPool = null;
699+
_bitArray = bitArray;
700+
_rentedBuffer = null;
721701
}
722-
if ((bitBlock & 255) == 0)
702+
703+
public BitArray(int spanLength, uint[] rentedBuffer, ArrayPool<uint> arrayPool) : this()
723704
{
724-
bitBlock >>= 8;
725-
leastSignificantBit |= 8;
705+
_arrayPool = arrayPool;
706+
_bitArray = rentedBuffer.AsSpan(0, spanLength);
707+
_rentedBuffer = rentedBuffer;
726708
}
727-
if ((bitBlock & 15) == 0)
709+
710+
public Span<uint> Span => _bitArray;
711+
public ArrayPool<uint> ArrayPool => _arrayPool;
712+
713+
public void SetMemberIndex(int memberMapIndex) =>
714+
_bitArray[memberMapIndex >> 5] |= 1U << (memberMapIndex & 31);
715+
716+
public void Dispose()
728717
{
729-
bitBlock >>= 4;
730-
leastSignificantBit |= 4;
718+
if (_isDisposed)
719+
return;
720+
721+
if (_rentedBuffer != null)
722+
{
723+
_arrayPool.Return(_rentedBuffer);
724+
}
725+
_isDisposed = true;
731726
}
732-
if ((bitBlock & 3) == 0)
727+
}
728+
729+
public static (int BitArrayLength, bool UseStackAlloc) GetBitArrayLength(int membersCount)
730+
{
731+
var length = (membersCount + 31) >> 5;
732+
return (length, length <= 8); // Use stackalloc for up to 256 members
733+
}
734+
735+
public static BitArray GetBitArray(Span<uint> span) =>
736+
new(ResetSpan(span));
737+
738+
public static BitArray GetBitArray(int length)
739+
{
740+
var rentedBuffer = ArrayPool<uint>.Shared.Rent(length);
741+
ResetSpan(rentedBuffer);
742+
743+
return new(length, rentedBuffer, ArrayPool<uint>.Shared);
744+
}
745+
746+
private static Span<uint> ResetSpan(Span<uint> span)
747+
{
748+
for (var i = 0; i < span.Length; i++)
733749
{
734-
bitBlock >>= 2;
735-
leastSignificantBit |= 2;
750+
span[i] = 0;
736751
}
737-
return leastSignificantBit - (int)(bitBlock & 1);
752+
753+
return span;
738754
}
739755
}
740756
}

0 commit comments

Comments
 (0)