-
Notifications
You must be signed in to change notification settings - Fork 1.3k
CSHARP-5666: Remove GetBitArray allocations in BsonClassMapSerializer.DeserializeClass #1767
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
….DeserializeClass
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall it looks good, just a small number of comments.
{ | ||
public static uint[] GetBitArray(int memberCount) | ||
internal ref struct BitArray() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should choose a different name so there is no confusion with System.Collections.BitArray
?
Not sure it's completely necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's minor but still a valid point.
Changed to MemebersBitArray
. Not sure about this naming though. Please suggest better alternatives if there are any.
bitArray[bitArrayLength] = ~0U << bitArrayOffset; // set unused bits to 1 | ||
return bitArray; | ||
} | ||
private readonly ArrayPool<uint> _arrayPool; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see you've started using ArrayPool
here, are we going to use this instead of ThreadStaticBuffer
from now on? I would agree to that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so.
Would be good to have a quick benchmark to ensure there are no significant performance differences.
Another reason I didn't used ThreadStaticBuffer
is because ThreadStaticBuffer
is limited to byte type.
[InlineData(1024, 993)] | ||
[InlineData(1024, 1000)] | ||
[InlineData(1024, 1023)] | ||
public void Deserialize_should_throw_FormatException_when_no_required_element_is_found(int membersCount, int missingMemberIndex) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should rename it to
Deserialize_should_throw_FormatException_when_required_element_is_not_found
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, done.
By the way, have we tried benchmarking what is the difference in allocation on a test case? |
return new(length, rentedBuffer, ArrayPool<uint>.Shared); | ||
} | ||
|
||
private static Span<uint> ResetSpan(Span<uint> span) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use Span.Clear
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Moved clearing to BitArray ctor for simplicity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good. I have some minor suggestions. Let me know what you think.
var memberMapBitArray = FastMemberMapHelper.GetBitArray(allMemberMaps.Count); | ||
|
||
var (bitArrayLength, useStackAlloc) = FastMemberMapHelper.GetMembersBitArrayLength(_classMap.AllMemberMaps.Count); | ||
using var bitArray = useStackAlloc ? FastMemberMapHelper.GetMembersBitArray(stackalloc uint[bitArrayLength]) : FastMemberMapHelper.GetMembersBitArray(bitArrayLength); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was confused by the name bitArrayLength
because it sounded like it was the number of bits, but it's actually the number of uints
.
I suggest some renaming:
var (lengthInUInts, useStackAlloc) = FastMemberMapHelper.GetLengthInUInts(allMemberMaps.Count);
using var bitArray = useStackAlloc ? FastMemberMapHelper.GetMembersBitArray(stackalloc uint[lengthInUInts]) : FastMemberMapHelper.GetMembersBitArray(lengthInUInts);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, done.
} | ||
if ((bitBlock & 15) == 0) | ||
|
||
public MembersBitArray(int spanLength, uint[] rentedBuffer, ArrayPool<uint> arrayPool) : this() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing in BOTH the rentedBuffer
and the arrayPool
it came from how about just passing in the arrayPool
?
public MembersBitArray(int lengthInUInts, ArrayPool<uint> arrayPool) : this()
{
_arrayPool = arrayPool;
_rentedBuffer = _arrayPool.Rent(lengthInUInts);
_bitArray = _rentedBuffer.AsSpan(0, lengthInUInts);
_bitArray.Clear();
}
Also suggest renaming spanLength
to lengthInUInts
to standardize on a name that clearly indicates it is the length in uints
and not the length in bits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, it's cleaner. Done.
{ | ||
var length = (membersCount + 31) >> 5; | ||
return (length, length <= 8); // Use stackalloc for up to 256 members | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some suggested renamings for clarity:
public static (int LengthInUInts, bool UseStackAlloc) GetLengthInUInts(int membersCoun
{
var lengthInUInts = (membersCount + 31) >> 5;
return (lengthInUInts, lengthInUInts <= 8); // Use stackalloc for up to 256 members
}
new(span); | ||
|
||
public static MembersBitArray GetMembersBitArray(int length) => | ||
new(length, ArrayPool<uint>.Shared.Rent(length), ArrayPool<uint>.Shared); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggested refactoring to only pass the ArrayPool
:
public static MembersBitArray GetMembersBitArray(int lengthInUInts) =>
new(lengthInUInts, ArrayPool<uint>.Shared);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor changes requested.
@@ -149,7 +151,9 @@ public TClass DeserializeClass(BsonDeserializationContext context) | |||
var discriminatorConvention = _classMap.GetDiscriminatorConvention(); | |||
var allMemberMaps = _classMap.AllMemberMaps; | |||
var extraElementsMemberMapIndex = _classMap.ExtraElementsMemberMapIndex; | |||
var memberMapBitArray = FastMemberMapHelper.GetBitArray(allMemberMaps.Count); | |||
|
|||
var (lengthInUInts, useStackAlloc) = FastMemberMapHelper.GetLengthInUInts(_classMap.AllMemberMaps.Count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to mention that you could just use the existing allMemberMaps
variable instead of _classMap.AllMemberMaps
.
} | ||
if ((bitBlock & 15) == 0) | ||
|
||
public MembersBitArray(int lengthInUints, ArrayPool<uint> arrayPool) : this() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Standardize capitalization as lengthInUInts
?
|
||
public static (int LengthInUInts, bool UseStackAlloc) GetLengthInUInts(int membersCount) | ||
{ | ||
var length = (membersCount + 31) >> 5; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename length
to lengthInUInts
?
public static MembersBitArray GetMembersBitArray(Span<uint> span) => | ||
new(span); | ||
|
||
public static MembersBitArray GetMembersBitArray(int length) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename length
to lengthInUInts
?
No description provided.