Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

stephentoub
Copy link
Member

StringBuilder.Replace(string, string, ...) currently walks character-by-character to find the next location of the oldValue. This change adds the use of MemoryExtensions.IndexOf to search for the oldValue, and only falls back to the character-by-character walk when it nears and needs to account for a chunk boundary. It also switches to track replacement indices using more stack space and the array pool (via ValueListBuilder) rather than less stack space and array allocations.

Using the benchmark from #80952:

Method Toolchain Mean Error StdDev Ratio Allocated Alloc Ratio
Replace_StringReplace \main\corerun.exe 43.71 us 0.537 us 0.476 us 1.00 227.96 KB 1.00
Replace_StringBuilder \main\corerun.exe 507.24 us 3.251 us 2.882 us 11.60 62.11 KB 0.27
Replace_StringReplace \pr\corerun.exe 39.38 us 0.440 us 0.411 us 0.90 227.96 KB 1.00
Replace_StringBuilder \pr\corerun.exe 24.16 us 0.213 us 0.189 us 0.55 58.9 KB 0.26

Fixes #80952

StringBuilder.Replace(string, string, ...) currently walks character-by-character to find the next location of the oldValue.  This change adds the use of MemoryExtensions.IndexOf to search for the oldValue, and only falls back to the character-by-character walk when it nears and needs to account for a chunk boundary.  It also switches to track replacement indices using more stack space and the array pool (via ValueListBuilder) rather than less stack space and array allocations.
@ghost
Copy link

ghost commented Jan 24, 2023

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Issue Details

StringBuilder.Replace(string, string, ...) currently walks character-by-character to find the next location of the oldValue. This change adds the use of MemoryExtensions.IndexOf to search for the oldValue, and only falls back to the character-by-character walk when it nears and needs to account for a chunk boundary. It also switches to track replacement indices using more stack space and the array pool (via ValueListBuilder) rather than less stack space and array allocations.

Using the benchmark from #80952:

Method Toolchain Mean Error StdDev Ratio Allocated Alloc Ratio
Replace_StringReplace \main\corerun.exe 43.71 us 0.537 us 0.476 us 1.00 227.96 KB 1.00
Replace_StringBuilder \main\corerun.exe 507.24 us 3.251 us 2.882 us 11.60 62.11 KB 0.27
Replace_StringReplace \pr\corerun.exe 39.38 us 0.440 us 0.411 us 0.90 227.96 KB 1.00
Replace_StringBuilder \pr\corerun.exe 24.16 us 0.213 us 0.189 us 0.55 58.9 KB 0.26

Fixes #80952

Author: stephentoub
Assignees: -
Labels:

area-System.Runtime, tenet-performance

Milestone: 8.0.0

@stephentoub
Copy link
Member Author

@adamsitnik, the assert issue is fixed. Can you take another look? Thanks.

Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

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

LGTM, thank you very much @stephentoub !


Span<int> replacements = stackalloc int[5]; // A list of replacement positions in a chunk to apply
int replacementsCount = 0;
var replacements = new ValueListBuilder<int>(stackalloc int[128]); // A list of replacement positions in a chunk to apply
Copy link
Member

@adamsitnik adamsitnik Jan 27, 2023

Choose a reason for hiding this comment

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

great use case for ValueListBuilder 👍 I am surprised that the temporary array allocation survived for so long in this repo ;)

if (replacementsCount >= replacements.Length)
{
int[] tmp = new int[replacements.Length * 3 / 2 + 4]; // Grow by ~1.5x, but more in the beginning
replacements.CopyTo(tmp);
replacements = tmp;
}

{
indexInChunk++;
--count;
if (StartsWith(chunk, indexInChunk, count, oldValue))
Copy link
Member

Choose a reason for hiding this comment

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

this method is the only method using StartsWith, would we gain anything from inlining it? Would you mind running the benchmarks if you still have this branch built on your machine? If not, it's not worth your time (I am just curious and it's really hard to suggest any improvements for this PR)

Copy link
Member Author

Choose a reason for hiding this comment

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

if you still have this branch built on your machine

I don't. We can follow-up on your question separately, though.

@stephentoub stephentoub merged commit f388963 into dotnet:main Jan 27, 2023
@stephentoub stephentoub deleted the improvesbreplace branch January 27, 2023 18:15
@ghost ghost locked as resolved and limited conversation to collaborators Feb 26, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lacking performance of StringBuilder.Replace
2 participants