Skip to content

CSHARP-5611: Eliminate the temporary byte array allocation in ObjectId.ToString#1706

Open
ugurpelister wants to merge 2 commits intomongodb:mainfrom
ugurpelister:main
Open

CSHARP-5611: Eliminate the temporary byte array allocation in ObjectId.ToString#1706
ugurpelister wants to merge 2 commits intomongodb:mainfrom
ugurpelister:main

Conversation

@ugurpelister
Copy link

ObjectId's ToString method is among the highest of all highly frequently used methods in this library. The temporary byte array allocation that takes place inside ToString is an unnecessary one that adds workload on the GC.

Given that the same functionality can be achieved by a leaner implementation for .NET6+ and .NETStandard2.1, we propose a Span-based implementation for these two platforms so that the resulting string can by created without leaving any garbage. The old implementation can continue to be used on .NET Framework 4.7.2.

@ugurpelister ugurpelister requested a review from a team as a code owner June 7, 2025 18:43
@ugurpelister ugurpelister requested review from sanych-sun and removed request for a team June 7, 2025 18:43
@ugurpelister ugurpelister marked this pull request as draft June 7, 2025 19:05
@ugurpelister ugurpelister marked this pull request as ready for review June 7, 2025 19:21
@damieng
Copy link
Contributor

damieng commented Jun 8, 2025

I think like the previous two PRs for this there this is going to regress on big endian for very real-world gains.

@ugurpelister
Copy link
Author

I think like the previous two PRs for this there this is going to regress on big endian for very real-world gains.

Specifically comparing to the existing implementation of ToByteArray, what do you think would cause the big endian-related regress in this span implementation?

{
if (destination.Length < 12)
{
throw new ArgumentException("Not enough room in destination span.", "destination");
Copy link
Contributor

Choose a reason for hiding this comment

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

I know that this is against the existing convention in the file, but for new methods we use should use nameof(...).

}

#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
/// <summary>
Copy link
Contributor

Choose a reason for hiding this comment

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

There is no need for #if, we include the System.Memory package.

throw new ArgumentException("Not enough room in destination span.", "destination");
}

Span<byte> span = stackalloc byte[12];
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should reuse the existing ToString implementation, to avoid the Span allocation (even thought it's on stack) and for easier review.

While ToString() should just fallback to ToCharSpan, as it does already, but for all TFMs.

public override string ToString()
{
#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
return string.Create(24, this, static (span, input) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: expression-bodied method

/// </summary>
/// <param name="destination">The destination byte span.</param>
/// <exception cref="ArgumentException">Not enough room in destination span.</exception>
public void ToByteSpan(Span<byte> destination)
Copy link
Contributor

Choose a reason for hiding this comment

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

For completeness, please add internal ObjectId(ReadOnlySpan<byte> span) contstructor
And appropriate tests.

#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
Span<char> span = new char[24];
objectId.ToCharSpan(span);
Assert.True(span.SequenceEqual("0102030405060708090a0b0c"));
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add separate tests for the new methods, testing different cases:
ToByteSpan_should_return_expcted_results(byte[] data, byte[] expectedBytes)
ToCharSpan_should_return_expected_results(byte[] data, string expectedChars) (this one can also test Tostring)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants