Skip to content

Commit

Permalink
Avoid a small int[] allocation in StringBuilder.Replace (#60406)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephentoub authored Oct 18, 2021
1 parent 862a90f commit fc3787d
Showing 1 changed file with 10 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1970,7 +1970,7 @@ public StringBuilder Replace(string oldValue, string? newValue, int startIndex,

newValue ??= string.Empty;

int[]? replacements = null; // A list of replacement positions in a chunk to apply
Span<int> replacements = stackalloc int[5]; // A list of replacement positions in a chunk to apply
int replacementsCount = 0;

// Find the chunk, indexInChunk for the starting point
Expand All @@ -1985,13 +1985,11 @@ public StringBuilder Replace(string oldValue, string? newValue, int startIndex,
// Push it on the replacements array (with growth), we will do all replacements in a
// given chunk in one operation below (see ReplaceAllInChunk) so we don't have to slide
// many times.
if (replacements == null)
if (replacementsCount >= replacements.Length)
{
replacements = new int[5];
}
else if (replacementsCount >= replacements.Length)
{
Array.Resize(ref replacements, replacements.Length * 3 / 2 + 4); // Grow by ~1.5x, but more in the beginning
int[] tmp = new int[replacements.Length * 3 / 2 + 4]; // Grow by ~1.5x, but more in the beginning
replacements.CopyTo(tmp);
replacements = tmp;
}
replacements[replacementsCount++] = indexInChunk;
indexInChunk += oldValue.Length;
Expand All @@ -2009,8 +2007,7 @@ public StringBuilder Replace(string oldValue, string? newValue, int startIndex,
int index = indexInChunk + chunk.m_ChunkOffset;

// See if we accumulated any replacements, if so apply them.
Debug.Assert(replacements != null || replacementsCount == 0, "replacements was null and replacementsCount != 0");
ReplaceAllInChunk(replacements, replacementsCount, chunk, oldValue.Length, newValue);
ReplaceAllInChunk(replacements.Slice(0, replacementsCount), chunk, oldValue.Length, newValue);
// The replacement has affected the logical index. Adjust it.
index += ((newValue.Length - oldValue.Length) * replacementsCount);
replacementsCount = 0;
Expand Down Expand Up @@ -2162,16 +2159,15 @@ private unsafe void Insert(int index, char* value, int valueCount)
/// Replaces strings at specified indices with a new string in a chunk.
/// </summary>
/// <param name="replacements">The list of indices, relative to the beginning of the chunk, to remove at.</param>
/// <param name="replacementsCount">The number of replacements to make.</param>
/// <param name="sourceChunk">The source chunk.</param>
/// <param name="removeCount">The number of characters to remove at each replacement.</param>
/// <param name="value">The string to insert at each replacement.</param>
/// <remarks>
/// This routine is very efficient because it does replacements in bulk.
/// </remarks>
private void ReplaceAllInChunk(int[]? replacements, int replacementsCount, StringBuilder sourceChunk, int removeCount, string value)
private void ReplaceAllInChunk(ReadOnlySpan<int> replacements, StringBuilder sourceChunk, int removeCount, string value)
{
if (replacementsCount <= 0)
if (replacements.IsEmpty)
{
return;
}
Expand All @@ -2180,9 +2176,8 @@ private void ReplaceAllInChunk(int[]? replacements, int replacementsCount, Strin
{
fixed (char* valuePtr = value)
{
Debug.Assert(replacements != null, "replacements was null when replacementsCount > 0");
// calculate the total amount of extra space or space needed for all the replacements.
long longDelta = (value.Length - removeCount) * (long)replacementsCount;
long longDelta = (value.Length - removeCount) * (long)replacements.Length;
int delta = (int)longDelta;
if (delta != longDelta)
{
Expand All @@ -2206,7 +2201,7 @@ private void ReplaceAllInChunk(int[]? replacements, int replacementsCount, Strin
ReplaceInPlaceAtChunk(ref targetChunk!, ref targetIndexInChunk, valuePtr, value.Length);
int gapStart = replacements[i] + removeCount;
i++;
if (i >= replacementsCount)
if ((uint)i >= replacements.Length)
{
break;
}
Expand Down

0 comments on commit fc3787d

Please sign in to comment.