Skip to content
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

Refactor use of FastAllocateString #110929

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions src/libraries/System.Private.CoreLib/src/System/BitConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -798,21 +798,20 @@ public static string ToString(byte[] value, int startIndex, int length)
// (int.MaxValue / 3) == 715,827,882 Bytes == 699 MB
ArgumentOutOfRangeException.ThrowIfGreaterThan(length, int.MaxValue / 3);

string result = string.FastAllocateString(length * 3 - 1);
string result = string.AllocateInternal(length * 3 - 1, out Span<char> resultSpan);

var dst = new Span<char>(ref result.GetRawStringData(), result.Length);
var src = new ReadOnlySpan<byte>(value, startIndex, length);
int i = 0;
int j = 0;
byte b = src[i++];
dst[j++] = HexConverter.ToCharUpper(b >> 4);
dst[j++] = HexConverter.ToCharUpper(b);
resultSpan[j++] = HexConverter.ToCharUpper(b >> 4);
resultSpan[j++] = HexConverter.ToCharUpper(b);
while (i < src.Length)
{
b = src[i++];
dst[j++] = '-';
dst[j++] = HexConverter.ToCharUpper(b >> 4);
dst[j++] = HexConverter.ToCharUpper(b);
resultSpan[j++] = '-';
resultSpan[j++] = HexConverter.ToCharUpper(b >> 4);
resultSpan[j++] = HexConverter.ToCharUpper(b);
}

return result;
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/System.Private.CoreLib/src/System/Convert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2355,11 +2355,11 @@ public static string ToBase64String(ReadOnlySpan<byte> bytes, Base64FormattingOp
bool insertLineBreaks = (options == Base64FormattingOptions.InsertLineBreaks);
int outputLength = ToBase64_CalculateAndValidateOutputLength(bytes.Length, insertLineBreaks);

string result = string.FastAllocateString(outputLength);
string result = string.AllocateInternal(outputLength, out Span<char> resultSpan);

if (Vector128.IsHardwareAccelerated && !insertLineBreaks && bytes.Length >= Base64VectorizationLengthThreshold)
{
ToBase64CharsLargeNoLineBreaks(bytes, new Span<char>(ref result.GetRawStringData(), result.Length), result.Length);
ToBase64CharsLargeNoLineBreaks(bytes, resultSpan, result.Length);
}
else
{
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/System.Private.CoreLib/src/System/Enum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1945,8 +1945,8 @@ private static bool TryFormatPrimitiveNonDefault<TUnderlying, TStorage>(RuntimeT
foundItems = foundItems.Slice(0, foundItemsCount);
int length = GetMultipleEnumsFlagsFormatResultLength(resultLength, foundItemsCount);

result = string.FastAllocateString(length);
WriteMultipleFoundFlagsNames(names, foundItems, new Span<char>(ref result.GetRawStringData(), result.Length));
result = string.AllocateInternal(length, out Span<char> resultSpan);
WriteMultipleFoundFlagsNames(names, foundItems, resultSpan);
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/libraries/System.Private.CoreLib/src/System/Exception.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,7 @@ public override string ToString()
}

// Create the string
string result = string.FastAllocateString(length);
Span<char> resultSpan = new Span<char>(ref result.GetRawStringData(), result.Length);
string result = string.AllocateInternal(length, out Span<char> resultSpan);

// Fill it in
Write(className, ref resultSpan);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,8 @@ internal static string Format(DateTime dateTime, string? format, IFormatProvider

internal static string Format(DateTime dateTime, string? format, IFormatProvider? provider, TimeSpan offset)
{
string result;
Span<char> resultSpan;
DateTimeFormatInfo dtfi;

if (string.IsNullOrEmpty(format))
Expand All @@ -929,17 +931,17 @@ internal static string Format(DateTime dateTime, string? format, IFormatProvider
{
if (IsTimeOnlySpecialCase(dateTime, dtfi))
{
string str = string.FastAllocateString(FormatSLength);
TryFormatS(dateTime, new Span<char>(ref str.GetRawStringData(), str.Length), out int charsWritten);
result = string.AllocateInternal(FormatSLength, out resultSpan);
TryFormatS(dateTime, resultSpan, out int charsWritten);
Debug.Assert(charsWritten == FormatSLength);
return str;
return result;
}
else if (ReferenceEquals(dtfi, DateTimeFormatInfo.InvariantInfo))
{
string str = string.FastAllocateString(FormatInvariantGMinLength);
TryFormatInvariantG(dateTime, offset, new Span<char>(ref str.GetRawStringData(), str.Length), out int charsWritten);
result = string.AllocateInternal(FormatInvariantGMinLength, out resultSpan);
TryFormatInvariantG(dateTime, offset, resultSpan, out int charsWritten);
Debug.Assert(charsWritten == FormatInvariantGMinLength);
return str;
return result;
}
else
{
Expand All @@ -955,10 +957,10 @@ internal static string Format(DateTime dateTime, string? format, IFormatProvider
}
else if (ReferenceEquals(dtfi, DateTimeFormatInfo.InvariantInfo))
{
string str = string.FastAllocateString(FormatInvariantGMaxLength);
TryFormatInvariantG(dateTime, offset, new Span<char>(ref str.GetRawStringData(), str.Length), out int charsWritten);
result = string.AllocateInternal(FormatInvariantGMaxLength, out resultSpan);
TryFormatInvariantG(dateTime, offset, resultSpan, out int charsWritten);
Debug.Assert(charsWritten == FormatInvariantGMaxLength);
return str;
return result;
}
else
{
Expand All @@ -969,7 +971,6 @@ internal static string Format(DateTime dateTime, string? format, IFormatProvider
else if (format.Length == 1)
{
int charsWritten;
string str;
switch (format[0])
{
// Round trip format
Expand All @@ -981,24 +982,24 @@ internal static string Format(DateTime dateTime, string? format, IFormatProvider

// RFC1123 format
case 'r' or 'R':
str = string.FastAllocateString(FormatRLength);
TryFormatR(dateTime, offset, new Span<char>(ref str.GetRawStringData(), str.Length), out charsWritten);
Debug.Assert(charsWritten == str.Length);
return str;
result = string.AllocateInternal(FormatRLength, out resultSpan);
TryFormatR(dateTime, offset, resultSpan, out charsWritten);
Debug.Assert(charsWritten == result.Length);
return result;

// Sortable format
case 's':
str = string.FastAllocateString(FormatSLength);
TryFormatS(dateTime, new Span<char>(ref str.GetRawStringData(), str.Length), out charsWritten);
Debug.Assert(charsWritten == str.Length);
return str;
result = string.AllocateInternal(FormatSLength, out resultSpan);
TryFormatS(dateTime, resultSpan, out charsWritten);
Debug.Assert(charsWritten == result.Length);
return result;

// Universal time in sortable format
case 'u':
str = string.FastAllocateString(FormatuLength);
TryFormatu(dateTime, offset, new Span<char>(ref str.GetRawStringData(), str.Length), out charsWritten);
Debug.Assert(charsWritten == str.Length);
return str;
result = string.AllocateInternal(FormatuLength, out resultSpan);
TryFormatu(dateTime, offset, resultSpan, out charsWritten);
Debug.Assert(charsWritten == result.Length);
return result;

// Universal time in culture dependent format
case 'U':
Expand All @@ -1021,9 +1022,9 @@ internal static string Format(DateTime dateTime, string? format, IFormatProvider

var vlb = new ValueListBuilder<char>(stackalloc char[256]);
FormatCustomized(dateTime, format, dtfi, offset, ref vlb);
string resultString = vlb.AsSpan().ToString();
result = vlb.AsSpan().ToString();
vlb.Dispose();
return resultString;
return result;
}

internal static bool TryFormat<TChar>(DateTime dateTime, Span<TChar> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) where TChar : unmanaged, IUtfChar<TChar> =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3860,11 +3860,10 @@ private static ReadOnlySpan<byte> GetCultureName(int localeNameIndice)

private static string GetString(ReadOnlySpan<byte> buffer)
{
string result = string.FastAllocateString(buffer.Length);
var s = new Span<char>(ref result.GetRawStringData(), buffer.Length);
string result = string.AllocateInternal(buffer.Length, out Span<char> resultSpan);
for (int i = 0; i < buffer.Length; i++)
{
s[i] = (char)buffer[i];
resultSpan[i] = (char)buffer[i];
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,10 @@ internal static string ToLower(string s)
return s;
}

string result = string.FastAllocateString(s.Length);
var destination = new Span<char>(ref result.GetRawStringData(), result.Length);
string result = string.AllocateInternal(s.Length, out Span<char> resultSpan);
ReadOnlySpan<char> src = s;
src.Slice(0, i).CopyTo(destination);
ToLower(src.Slice(i), destination.Slice(i));
src.Slice(0, i).CopyTo(resultSpan);
ToLower(src.Slice(i), resultSpan.Slice(i));

return result;
}
Expand Down Expand Up @@ -99,11 +98,10 @@ internal static string ToUpper(string s)
return s;
}

string result = string.FastAllocateString(s.Length);
var destination = new Span<char>(ref result.GetRawStringData(), result.Length);
string result = string.AllocateInternal(s.Length, out Span<char> resultSpan);
ReadOnlySpan<char> src = s;
src.Slice(0, i).CopyTo(destination);
ToUpper(src.Slice(i), destination.Slice(i));
src.Slice(0, i).CopyTo(resultSpan);
ToUpper(src.Slice(i), resultSpan.Slice(i));

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,9 @@ private unsafe string ChangeCaseCommon<TConversion>(string source) where TConver
// This will necessarily allocate a new string, but let's try to stay within the managed (non-localization tables)
// conversion code path if we can.

string result = string.FastAllocateString(source.Length); // changing case uses simple folding: doesn't change UTF-16 code unit count
string result = string.AllocateInternal(source.Length, out Span<char> resultSpan); // changing case uses simple folding: doesn't change UTF-16 code unit count

// copy existing known-good data into the result
Span<char> resultSpan = new Span<char>(ref result.GetRawStringData(), result.Length);
source.AsSpan(0, (int)currIdx).CopyTo(resultSpan);

// and re-run the fast span-based logic over the remainder of the data
Expand All @@ -341,12 +340,11 @@ private unsafe string ChangeCaseCommon<TConversion>(string source) where TConver
// We reached non-ASCII data *or* the requested culture doesn't map ASCII data the same way as the invariant culture.
// In either case we need to fall back to the localization tables.

string result = string.FastAllocateString(source.Length); // changing case uses simple folding: doesn't change UTF-16 code unit count
string result = string.AllocateInternal(source.Length, out Span<char> resultSpan); // changing case uses simple folding: doesn't change UTF-16 code unit count

if (currIdx > 0)
{
// copy existing known-good data into the result
Span<char> resultSpan = new Span<char>(ref result.GetRawStringData(), result.Length);
source.AsSpan(0, (int)currIdx).CopyTo(resultSpan);
}

Expand Down
8 changes: 4 additions & 4 deletions src/libraries/System.Private.CoreLib/src/System/Guid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1176,12 +1176,12 @@ public string ToString([StringSyntax(StringSyntaxAttribute.GuidFormat)] string?
};
}

string guidString = string.FastAllocateString(guidSize);
string result = string.AllocateInternal(guidSize, out Span<char> resultSpan);

bool result = TryFormatCore(new Span<char>(ref guidString.GetRawStringData(), guidString.Length), out int bytesWritten, format);
Debug.Assert(result && bytesWritten == guidString.Length, "Formatting guid should have succeeded.");
bool success = TryFormatCore(resultSpan, out int bytesWritten, format);
Debug.Assert(success && bytesWritten == result.Length, "Formatting guid should have succeeded.");

return guidString;
return result;
}

public bool TryFormat(Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.GuidFormat)] ReadOnlySpan<char> format = default) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,16 @@ public static SearchValues<string> Create(ReadOnlySpan<string> values, bool igno

static string NormalizeIfNeeded(string value, bool ignoreCase)
{
string result = value;

if (ignoreCase && value.AsSpan().ContainsAnyExcept(s_allAsciiExceptLowercase))
{
string upperCase = string.FastAllocateString(value.Length);
int charsWritten = Ordinal.ToUpperOrdinal(value, new Span<char>(ref upperCase.GetRawStringData(), upperCase.Length));
Debug.Assert(charsWritten == upperCase.Length);
value = upperCase;
result = string.AllocateInternal(value.Length, out Span<char> resultSpan);
int charsWritten = Ordinal.ToUpperOrdinal(value, resultSpan);
Debug.Assert(charsWritten == result.Length);
}

return value;
return result;
}

static Span<string> RemoveUnreachableValues(Span<string> values, HashSet<string> unreachableValues)
Expand Down
11 changes: 9 additions & 2 deletions src/libraries/System.Private.CoreLib/src/System/String.cs
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,8 @@ public static string Create<TState>(int length, TState state, SpanAction<char, T
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
}

string result = FastAllocateString(length);
action(new Span<char>(ref result.GetRawStringData(), length), state);
string result = AllocateInternal(length, out Span<char> resultSpan);
action(resultSpan, state);
return result;
}

Expand Down Expand Up @@ -527,6 +527,13 @@ public static bool IsNullOrWhiteSpace([NotNullWhen(false)] string? value)
internal ref char GetRawStringData() => ref _firstChar;
internal ref ushort GetRawStringDataAsUInt16() => ref Unsafe.As<char, ushort>(ref _firstChar);

internal static string AllocateInternal(int length, out Span<char> resultSpan)
{
string result = FastAllocateString(length);
resultSpan = new Span<char>(ref result._firstChar, result.Length);
return result;
}
Copy link
Member

Choose a reason for hiding this comment

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

Should we rather use string.Create everywhere?

Copy link
Member

Choose a reason for hiding this comment

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

If the delegate can be inlined everywhere, otherwise there may be performance hot spots?


// Helper for encodings so they can talk to our buffer directly
// stringLength must be the exact size we'll expect
internal static unsafe string CreateStringFromEncoding(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,8 @@ public string ToString(int startIndex, int length)
}

AssertInvariants();
string result = string.FastAllocateString(length);
CopyTo(startIndex, new Span<char>(ref result.GetRawStringData(), result.Length), result.Length);
string result = string.AllocateInternal(length, out Span<char> resultSpan);
CopyTo(startIndex, resultSpan, resultSpan.Length);
return result;
}

Expand Down
Loading