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

StringBuilder.Replace with ReadOnlySpan<char> #93938

Merged

Conversation

TheMaximum
Copy link
Contributor

Converted the StringBuilder.Replace(string, string, int, int) method and underlaying methods to take ReadOnlySpan<char> as arguments instead. The Replace methods with string arguments are cast to spans (calling .AsSpan()) to use the same new method.

String-based tests still pass, as do the added ones for the ReadOnlySpan<char> variants. Was looking to see if I can somehow benchmark the implementations, but I can't figure out how to run these against the self-built runtime.

Fix #77837

Converted the StringBuilder.Replace(string, string, int, int) method
and underlaying methods to take ReadOnlySpan<char> as arguments
instead. The Replace methods with string arguments create spans to use
the same new method.

Fix dotnet#77837
@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Oct 24, 2023
@ghost
Copy link

ghost commented Oct 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

Converted the StringBuilder.Replace(string, string, int, int) method and underlaying methods to take ReadOnlySpan<char> as arguments instead. The Replace methods with string arguments are cast to spans (calling .AsSpan()) to use the same new method.

String-based tests still pass, as do the added ones for the ReadOnlySpan<char> variants. Was looking to see if I can somehow benchmark the implementations, but I can't figure out how to run these against the self-built runtime.

Fix #77837

Author: TheMaximum
Assignees: -
Labels:

area-System.Runtime, new-api-needs-documentation

Milestone: -

Moved null checks to the string-specific overloads, as spans won't be
null. (PR feedback)
@danmoseley
Copy link
Member

Re benchmarking, there are docs in dotnet/performance repo that may help.

Fixed code-style failure and implemented further feedback.
@TheMaximum
Copy link
Contributor Author

TheMaximum commented Oct 25, 2023

Re benchmarking, there are docs in dotnet/performance repo that may help.

I had found that documentation, @danmoseley, however when trying to run any benchmarks it kept complaining about SDK mismatches, which I wasn't able to solve for several days. Must admit that I now face a similar issue even building the solution as I synced the branch which now builds for the RTM version which is not publicly available (but this is a more recent issue)...

@danmoseley
Copy link
Member

OK, I don't work in this repo day to day any more, so @stephentoub may be more useful as he does this kind of benchmarking all the time.

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.

The code LGTM, but it needs a little of polishing before it's approved. PTAL at my comments.

however when trying to run any benchmarks it kept complaining about SDK mismatches, which I wasn't able to solve for several days

You can use the python 3 script provided by the dotnet/performance repository. It's going to download the SDK for you and run the benchmarks:

git clone https://github.com/dotnet/performance.git
cd performance
python3 .\scripts\benchmarks_ci.py -f net8.0 --filter '*yourFilter*' --corerun $pathToCoreRunWithoutChanges $pathToCoreRunWithChanges

But one thing to keep in mind is that benchmarking new APIs is PITA. To make your life easier, you can simply benchmark the existing string-based overload:

public StringBuilder Replace(string oldValue, string? newValue, int startIndex, int count)

To do that you will need to add new benchmarks to https://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.Text/Perf.StringBuilder.cs as we currently don't have benchmarks for this particular overload.

If you can't run the benchmarks or simply don't have the time to do it, please let me know.

@TheMaximum thank you for your contribution!

@ghost ghost added the needs-author-action An issue or pull request that requires more info or actions from the author. label Nov 3, 2023
@adamsitnik adamsitnik self-assigned this Nov 3, 2023
@ghost ghost removed the needs-author-action An issue or pull request that requires more info or actions from the author. label Nov 3, 2023
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.

It's almost ready 👍 Thank you @TheMaximum !

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 @TheMaximum !

@TheMaximum
Copy link
Contributor Author

@adamsitnik, I've been able to run the benchmarks using the following command:

python3 .\scripts\benchmarks_ci.py -f net8.0 --filter '*StringBuilder*Replace*' --corerun "D:\dotnet\runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe" "D:\dotnet\runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe"

Created a benchmark as follows:

[Benchmark]
public StringBuilder Replace_Strings()
{
    StringBuilder builder = new StringBuilder("initialvalue");

    builder.Replace("initial", "new");
    builder.Replace("value", "text");

    return builder;
}

The results are as follows below (runtime = with change / runtime-upstream = latest from dotnet/runtime:main). Seems like the new implementation is slightly slower than the existing one - might this be caused by calling .AsSpan() or maybe it has something to do with the changed implementation of the main replace methods? The issue was mainly centered about improving memory/GC usage when using ReadOnlySpan<char> (avoiding the need to cast to string).

I'm not 100% sure I'm reading this right, and also not sure if the benchmark is representative. Don't know if these results are satisfactory, or maybe you want to give some pointers (or run some yourself)? Not sure if these benchmarks are necessary either way (but wanted to attempt it anyway)...

Method Job Toolchain Mean Error StdDev Median Min Max Ratio RatioSD Gen0 Allocated Alloc Ratio
Replace_Strings Job-IBBWUN \runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 80.38 ns 1.591 ns 1.832 ns 80.35 ns 75.12 ns 83.28 ns 1.00 0.00 0.0082 104 B 1.00
Replace_Strings Job-GSLAPY \runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 86.53 ns 1.958 ns 2.255 ns 85.82 ns 83.87 ns 90.47 ns 1.08 0.04 0.0080 104 B 1.00

A second run seems to produce similar results:

Method Job Toolchain Mean Error StdDev Median Min Max Ratio RatioSD Gen0 Allocated Alloc Ratio
Replace_Strings Job-DXQHQN \runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 83.43 ns 2.414 ns 2.780 ns 83.89 ns 78.26 ns 88.03 ns 1.00 0.00 0.0081 104 B 1.00
Replace_Strings Job-NOAKZW \runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 89.14 ns 2.242 ns 2.582 ns 89.48 ns 83.64 ns 92.88 ns 1.07 0.05 0.0080 104 B 1.00

@@ -1950,6 +1961,23 @@ public bool Equals(ReadOnlySpan<char> span)
/// are removed from this builder.
/// </remarks>
public StringBuilder Replace(string oldValue, string? newValue, int startIndex, int count)
{
ArgumentException.ThrowIfNullOrEmpty(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 can just be ThrowIfNull. The called method has the check for empty.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It would change the exception message (I think) in case it's an empty string (SR.Arg_EmptySpan instead of SR.Argument_EmptyString), I don't know if that would be an issue? It might be confusing to the user who's using strings to get an exception message indicating an empty span?

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.

not sure if the benchmark is representative

The benchmark you have provided creates a relatively small StringBuilder that is internally backed by a single segment and performs two simple replace operations that do find the strings and replace them with shorter values.
It's OK, but we need more scenarios to be covered.

Ideally we would need:

Once we have that you can tell the python script to run the benchmarks more than once by adding --bdn-arguments "--launchCount 6 --memoryRandomization true". It will run every benchmark six times and try to apply some memory randomization (so the alignment of internal string builder buffer is different every time)

@TheMaximum
Copy link
Contributor Author

TheMaximum commented Nov 6, 2023

@adamsitnik, I've attempted to create the (micro)benchmarks for the cases you've described:

[Benchmark]
[Arguments(100)]
[Arguments(LOHAllocatedStringSize)]
public StringBuilder Replace_Strings_Simple(int length)
{
    StringBuilder builder = new StringBuilder(new string('a', length));
    builder.Replace("a", "b");

    return builder;
}

[Benchmark]
[Arguments(100)]
[Arguments(LOHAllocatedStringSize)]
public StringBuilder Replace_Strings_NoReplace(int length)
{
    StringBuilder builder = new StringBuilder(new string('a', length));
    builder.Replace("b", "test");

    return builder;
}

[Benchmark]
[Arguments(100)]
[Arguments(LOHAllocatedStringSize)]
public StringBuilder Replace_Strings_ReplaceShorter(int length)
{
    StringBuilder builder = new StringBuilder();
    builder.Insert(0, "ab", (length / 2));
    builder.Replace("ab", "a");

    return builder;
}

[Benchmark]
[Arguments(100)]
[Arguments(LOHAllocatedStringSize)]
public StringBuilder Replace_Strings_ReplaceLonger(int length)
{
    StringBuilder builder = new StringBuilder();
    builder.Insert(0, "ab", (length / 2));
    builder.Replace("ab", "zab");

    return builder;
}

I got the following results with executing them 6 times and applying memory randomization. To be honest, I'd like to leave the conclusions to you - as I'm not so sure I can make any myself (and I'm not 100% sure the benchmarks are correct)...

Method Job Toolchain length Mean Error StdDev Median Min Max Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
Replace_Strings_Simple Job-NLGUEL \runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100 1,520.31 ns 12.381 ns 37.838 ns 1,529.53 ns 1,435.22 ns 1,589.31 ns 1.00 0.00 0.0368 - - 496 B 1.00
Replace_Strings_Simple Job-HNVNGD \runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100 1,526.04 ns 11.463 ns 35.377 ns 1,533.88 ns 1,443.38 ns 1,611.74 ns 1.00 0.03 0.0367 - - 496 B 1.00
Replace_Strings_NoReplace Job-NLGUEL \runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100 42.07 ns 1.135 ns 3.685 ns 41.31 ns 37.00 ns 66.99 ns 1.00 0.00 0.0395 - - 496 B 1.00
Replace_Strings_NoReplace Job-HNVNGD \runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100 41.06 ns 0.412 ns 1.226 ns 40.91 ns 38.17 ns 47.19 ns 0.98 0.08 0.0394 - - 496 B 1.00
Replace_Strings_ReplaceShorter Job-NLGUEL \runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100 861.60 ns 7.113 ns 21.521 ns 861.16 ns 810.39 ns 930.71 ns 1.00 0.00 0.0296 - - 376 B 1.00
Replace_Strings_ReplaceShorter Job-HNVNGD \runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100 860.98 ns 13.823 ns 43.479 ns 858.90 ns 785.76 ns 1,129.19 ns 1.00 0.06 0.0290 - - 376 B 1.00
Replace_Strings_ReplaceLonger Job-NLGUEL \runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100 899.33 ns 8.389 ns 26.014 ns 899.36 ns 818.48 ns 968.44 ns 1.00 0.00 0.0438 - - 552 B 1.00
Replace_Strings_ReplaceLonger Job-HNVNGD \runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100 887.16 ns 7.845 ns 23.131 ns 894.88 ns 816.92 ns 928.70 ns 0.99 0.04 0.0431 - - 552 B 1.00
Replace_Strings_Simple Job-NLGUEL \runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100000 1,561,129.26 ns 9,994.000 ns 28,513.445 ns 1,562,408.75 ns 1,497,733.24 ns 1,633,194.32 ns 1.00 0.00 107.9545 107.9545 107.9545 400168 B 1.00
Replace_Strings_Simple Job-HNVNGD \runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100000 1,568,158.41 ns 10,759.941 ns 30,348.606 ns 1,571,989.01 ns 1,500,515.62 ns 1,658,951.88 ns 1.00 0.03 107.9545 107.9545 107.9545 400168 B 1.00
Replace_Strings_NoReplace Job-NLGUEL \runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100000 128,536.19 ns 435.035 ns 1,212.703 ns 128,243.42 ns 126,064.13 ns 132,801.07 ns 1.00 0.00 124.4919 124.4919 124.4919 400138 B 1.00
Replace_Strings_NoReplace Job-HNVNGD \runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100000 131,851.64 ns 1,884.796 ns 6,119.111 ns 129,304.45 ns 126,879.17 ns 156,769.66 ns 1.02 0.05 124.4792 124.4792 124.4792 400138 B 1.00
Replace_Strings_ReplaceShorter Job-NLGUEL \runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100000 892,371.89 ns 7,697.059 ns 23,983.806 ns 888,522.04 ns 833,729.93 ns 968,752.34 ns 1.00 0.00 58.8235 58.8235 58.8235 200218 B 1.00
Replace_Strings_ReplaceShorter Job-HNVNGD \runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100000 884,278.04 ns 7,294.555 ns 21,046.460 ns 883,833.31 ns 830,986.33 ns 949,464.84 ns 0.99 0.04 59.0278 59.0278 59.0278 200218 B 1.00
Replace_Strings_ReplaceLonger Job-NLGUEL \runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100000 851,331.92 ns 8,929.931 ns 27,692.793 ns 852,993.96 ns 790,693.26 ns 973,410.76 ns 1.00 0.00 88.8158 88.8158 88.8158 300310 B 1.00
Replace_Strings_ReplaceLonger Job-HNVNGD \runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe 100000 851,073.43 ns 6,466.335 ns 19,267.521 ns 853,501.10 ns 792,163.49 ns 893,834.56 ns 1.00 0.04 88.2353 88.2353 88.2353 300310 B 1.00
Outliers
  Perf_StringBuilder.Replace_Strings_NoReplace: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1      -> 8 outliers were detected (50.65 ns..69.67 ns)
  Perf_StringBuilder.Replace_Strings_NoReplace: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1               -> 10 outliers were detected (40.61 ns..41.37 ns, 45.82 ns..49.91 ns)
  Perf_StringBuilder.Replace_Strings_ReplaceShorter: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1 -> 1 outlier  was  detected (933.40 ns)
  Perf_StringBuilder.Replace_Strings_ReplaceShorter: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1          -> 7 outliers were detected (788.58 ns..791.53 ns, 969.41 ns..1.13 us)
  Perf_StringBuilder.Replace_Strings_ReplaceLonger: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1  -> 3 outliers were detected (821.10 ns, 831.56 ns, 971.04 ns)
  Perf_StringBuilder.Replace_Strings_ReplaceLonger: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1           -> 4 outliers were detected (819.55 ns..831.17 ns)
  Perf_StringBuilder.Replace_Strings_Simple: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1                  -> 1 outlier  was  detected (1.66 ms)
  Perf_StringBuilder.Replace_Strings_NoReplace: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1      -> 8 outliers were detected (126.07 us, 130.91 us..132.80 us)
  Perf_StringBuilder.Replace_Strings_NoReplace: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1               -> 17 outliers were detected (138.14 us..156.77 us)
  Perf_StringBuilder.Replace_Strings_ReplaceShorter: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1 -> 2 outliers were detected (968.20 us, 968.76 us)
  Perf_StringBuilder.Replace_Strings_ReplaceShorter: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1          -> 15 outliers were detected (830.99 us..849.87 us, 916.17 us..949.47 us)
  Perf_StringBuilder.Replace_Strings_ReplaceLonger: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime-upstream\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1  -> 19 outliers were detected (790.70 us..808.92 us, 895.26 us..973.41 us)
  Perf_StringBuilder.Replace_Strings_ReplaceLonger: PowerPlanMode=00000000-0000-0000-0000-000000000000, Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true, Toolchain=\runtime\artifacts\bin\testhost\net9.0-windows-Release-x64\shared\Microsoft.NETCore.App\9.0.0\corerun.exe, IterationTime=250.0000 ms, LaunchCount=6, MaxIterationCount=20, MemoryRandomization=True, MinIterationCount=15, WarmupCount=1           -> 8 outliers were detected (792.17 us..810.34 us, 893.84 us)

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.

The benchmark numbers LGTM, thank you for sharing the results @TheMaximum !

I've found two places where we could improve the comments. I'll apply my suggestions and merge the PR. Thanks!

…-replace-readonlyspan

# Conflicts:
#	src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Text/StringBuilderReplaceTests.cs
@adamsitnik adamsitnik merged commit 3628544 into dotnet:main Nov 27, 2023
173 of 176 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Dec 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Runtime community-contribution Indicates that the PR has been added by a community member new-api-needs-documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[API Proposal]: Stringbuilder.Replace(ROS oldValue, ROS newValue, int index, int count)
5 participants