Skip to content

Conversation

BorisDog
Copy link
Contributor

@BorisDog BorisDog commented Sep 1, 2025

No description provided.

@BorisDog BorisDog requested a review from a team as a code owner September 1, 2025 18:19
@BorisDog BorisDog requested review from papafe and removed request for a team September 1, 2025 18:19
papafe
papafe previously approved these changes Sep 2, 2025
Copy link
Contributor

@papafe papafe left a 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()
Copy link
Contributor

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.

Copy link
Contributor Author

@BorisDog BorisDog Sep 9, 2025

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done.

@papafe
Copy link
Contributor

papafe commented Sep 2, 2025

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)
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@rstam rstam left a 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);
Copy link
Contributor

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);

Copy link
Contributor Author

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()
Copy link
Contributor

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.

Copy link
Contributor Author

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
}
Copy link
Contributor

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);
Copy link
Contributor

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);                       

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@BorisDog BorisDog requested a review from rstam September 10, 2025 13:04
Copy link
Contributor

@rstam rstam left a 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);
Copy link
Contributor

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()
Copy link
Contributor

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;
Copy link
Contributor

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) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename length to lengthInUInts?

@rstam rstam self-requested a review September 10, 2025 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants