-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Add StringBuilder.Append(ROM<char>) to existing Append(ROS<char>) for perf #27427
Comments
Good catch. I would have also added allocation numbers which outline the unnecessary string allocation. |
Good call. I've updated it with the allocations, and I've also added a "longer" string to show that it gets worse as the string gets longer. ROS barely even grows as the string gets longer. ROM, not so much. |
Thanks. It would be interesting to find out if we have other performance critical paths in the BCL that accept an object and call ToString on it. |
Api looks good to me. Marking it as ready for review. |
You may have a GC hole here. Consider the proposed implementation, which logically expands to the below. public StringBuilder Append(ROM<char> rom) {
var span = rom.Span;
/* 'rom' is no longer referenced at this line */
return Append(span);
} If the A |
Also, if @jkotas or @Maoni0 or anybody else could comment on my GC hole theory above, that would be helpful. Perhaps I'm way off base here. But there's certainly no requirement that the caller keep the reference alive, so this seems like a variant on the classic " |
@stephentoub could probably take a look? |
There is no GC hole in the fragment at the top (when taken as a whole) because of the @GrabYourPitchforks I believe that you have written the rules that have to be followed for safe use of IIRC, the rules said that the caller is responsible for ensuring the correct lifetime. It is actually not very practical to call |
It's a work in progress: dotnet/docs#4823 cc @rpetrusha, @mairaw |
For the GC.KeepAlive boxing issue, what about adding a generic GC.KeepAlive that could take structs? |
@jkotas They were more guidelines rather than strict rules. But perhaps you're right - this is more of a problem for the caller to handle and the callees shouldn't be responsible for handling it. I'll spin up a separate conversation about it so that we don't derail the discussion for this particular work item. |
@GrabYourPitchforks did clarity emerge sufficient to take this further (or not)? |
@danmosemsft Don't block this work item on my comment. |
OK let's get this through review then |
We asked ourselves whether we need both overloads ( Approved as proposed. |
@eerhardt I guess we can mark this up for grabs? |
Yep - done. |
This is easy to add but how do you test it? If you don't have the ROM overload it'll just fallback to the object overload which is the reason there's a problem in the first place and it'll succeed anyway. Should the test do a reflection check to find the specific overload wanted is present and assert if it isn't? |
The only benefit of the overload is performance, so you'd validate that benefit with a performance test. Functionally we'd make sure we have StringBuilder.Append coverage that passes in both object and ReadOnlyMemory, and make sure they both behave correctly.
The build system can do that at compile time, failing if the method is in the ref but not in the src. Reflection should not be used. |
StirngBuilder is in corelib so no ref project. How and where is the exposed surface checked? |
In corefx.
Add both and compare. |
You can use https://apisof.net/ to check in which assembly an API is exposed. StringBuilder is exposed in System.Runtime/ref, implemented in System.Private.CoreLib, tests for it are in System.Runtime.Tests and performance tests in System.Runtime.Performance.Tests. |
Results in:
The reason
StringBuilder.Append(ROM<char>)
even works is becauseAppend
has an overload that takes anobject
, which just turns around and calls.ToString()
on the object.ROM<char>
overrides the ToString, and returnsstring.Substring
, which is going to make a copy of the string. Thus resulting in worse performance.I'm sure I'm not the last person who is going to make this mistake assuming that
StringBuilder.Append(ROM<char>)
should "just work" as expected.Proposal
A simple fix for this would be to add another overload to
StringBuilder.Append
that takes aReadOnlyMemory<char>
and just calls the existingAppend(ReadOnlySpan<char>)
method, which doesn't need to copy the string before appending./cc @GrabYourPitchforks @ahsonkhan @codemzs
The text was updated successfully, but these errors were encountered: