Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8e1e217

Browse files
committed
CSHARP-5666: Remove GetBitArray allocations in BsonClassMapSerializer.DeserializeClass
1 parent dd3f679 commit 8e1e217

File tree

2 files changed

+274
-78
lines changed

2 files changed

+274
-78
lines changed

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

Lines changed: 82 additions & 77 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 (lengthInUInts, useStackAlloc) = FastMemberMapHelper.GetLengthInUInts(allMemberMaps.Count);
156+
using var bitArray = useStackAlloc ? FastMemberMapHelper.GetMembersBitArray(stackalloc uint[lengthInUInts]) : FastMemberMapHelper.GetMembersBitArray(lengthInUInts);
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,48 +684,63 @@ 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-
publicstaticuint[]GetBitArray(intmemberCount)
689+
internalrefstructMembersBitArray()
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)
718-
{
719-
bitBlock >>= 16;
720-
leastSignificantBit |= 16;
721-
}
722-
if ((bitBlock & 255) == 0)
696+
public MembersBitArray(Span<uint> bitArray) : this()
723697
{
724-
bitBlock >>= 8;
725-
leastSignificantBit |= 8;
698+
_arrayPool = null;
699+
_bitArray = bitArray;
700+
_rentedBuffer = null;
701+
702+
_bitArray.Clear();
726703
}
727-
if ((bitBlock & 15) == 0)
704+
705+
public MembersBitArray(int lengthInUInts, ArrayPool<uint> arrayPool) : this()
728706
{
729-
bitBlock >>= 4;
730-
leastSignificantBit |= 4;
707+
_arrayPool = arrayPool;
708+
_rentedBuffer = arrayPool.Rent(lengthInUInts);
709+
_bitArray = _rentedBuffer.AsSpan(0, lengthInUInts);
710+
711+
_bitArray.Clear();
731712
}
732-
if ((bitBlock & 3) == 0)
713+
714+
public Span<uint> Span => _bitArray;
715+
public ArrayPool<uint> ArrayPool => _arrayPool;
716+
717+
public void SetMemberIndex(int memberMapIndex) =>
718+
_bitArray[memberMapIndex >> 5] |= 1U << (memberMapIndex & 31);
719+
720+
public void Dispose()
733721
{
734-
bitBlock >>= 2;
735-
leastSignificantBit |= 2;
722+
if (_isDisposed)
723+
return;
724+
725+
if (_rentedBuffer != null)
726+
{
727+
_arrayPool.Return(_rentedBuffer);
728+
}
729+
_isDisposed = true;
736730
}
737-
return leastSignificantBit - (int)(bitBlock & 1);
738731
}
732+
733+
public static (int LengthInUInts, bool UseStackAlloc) GetLengthInUInts(int membersCount)
734+
{
735+
var lengthInUInts = (membersCount + 31) >> 5;
736+
return (lengthInUInts, lengthInUInts <= 8); // Use stackalloc for up to 256 members
737+
}
738+
739+
public static MembersBitArray GetMembersBitArray(Span<uint> span) =>
740+
new(span);
741+
742+
public static MembersBitArray GetMembersBitArray(int lengthInUInts) =>
743+
new(lengthInUInts, ArrayPool<uint>.Shared);
739744
}
740745
}
741746
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /