-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Improve string builder caches #7093
Improve string builder caches #7093
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty reasonable. I'm thinking about one alternative way to resize: maintaining an array of StringBuilders of various sizes and using one that seems appropriate for the given situation. Then, if we find we need a bigger one, we can either add a pointer to a smaller one (if relevant) (don't need to copy anything or allocate) or move everything to the next larger StringBuilder (need to copy, but it avoids fragmentation and is probably simpler). I'm not sure it's worth it, but I thought I'd mention it as a possibility.
return sb; | ||
} | ||
} | ||
} | ||
return new StringBuilder(capacity); | ||
|
||
StringBuilder stringBuilder = new StringBuilder(capacity); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be the power of 2 greater than capacity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have applied power of 2 logic only to ReuseableStringBuilder as according to captured statistics, SB from StringBuilderCache
uses only small capacity and is not as heavily utilized.
|
||
/// <summary> | ||
/// Returns the shared builder for the next caller to use. | ||
/// ** CALLERS, DO NOT USE THE BUILDER AFTER RELEASING IT HERE! ** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some ideas to ensure correct usage:
- Null out fields on
ReuseableStringBuilder
to catch use-after-free errors. - Ensure those fields are non-null in this method, to catch double-free errors.
Maybe just in debug builds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have nulled those fields on Release
and added some asserts.
Co-authored-by: Forgind <Forgind@users.noreply.github.com> Co-authored-by: Drew Noakes <git@drewnoakes.com> Co-authored-by: Rainer Sigwald <raines@microsoft.com>
…thub.com/rokonec/msbuild into rokonec/2697-improve-reusable-sb-factory
#endif | ||
FrameworkErrorUtilities.VerifyThrowInternalNull(returning._borrowedBuilder, nameof(returning._borrowedBuilder) + " can not be null."); | ||
|
||
StringBuilder returningBuilder = returning._borrowedBuilder!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StringBuilder returningBuilder = returning._borrowedBuilder!; | |
StringBuilder returningBuilder = returning._borrowedBuilder; |
The !
can be avoided here by annotating VerifyThrowInternalNull
as:
internal static void VerifyThrowInternalNull([NotNull] object? parameter, string parameterName)
This looks a bit weird, but it means:
- The argument may be null when it is passed in
- The argument will not be null when normal flow continues
As an aside (likely for another PR) if we're on C# 10 then it may be possible to use the CallerArgumentExpressionAttribute
on parameterName
:
internal static void VerifyThrowInternalNull([NotNull] object? parameter, [CallerArgumentExpressionAttribute] string parameterName = null)
Meaning the call could be simplified to:
FrameworkErrorUtilities.VerifyThrowInternalNull(returning._borrowedBuilder);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather address this in another PR.
FrameworkErrorUtilities.VerifyThrowInternalNull
needs some rethinking. It should be supporting nullable, but currently the lines:
msbuild/src/Framework/ErrorUtilities.cs
Line 23 in 93e4693
private static readonly bool s_throwExceptions = string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDONOTTHROWINTERNAL")); |
along with
msbuild/src/Framework/ErrorUtilities.cs
Lines 69 to 72 in 93e4693
if (s_throwExceptions) | |
{ | |
throw new InternalErrorException(string.Format(message, args), innerException); | |
} |
breaks the rules "The argument will not be null when normal flow continues".
I would like to question usefulness of
MSBUILDDONOTTHROWINTERNAL
as this means "lets MSBuild fail at next line when accessing null ref object".@rainersigwald do people use it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't realise it was conditional. Still, I think it would be ok to annotate it as though the return value is non-null given that's clearly the intent. NRT doesn't give hard guarantees. It's mostly about communicating intent in code and make bugs more obvious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree about doing this in another PR (and closing #5163 while we're at it). I pushed up a branch with a half-hearted attempt at annotating ErrorUtilities that I put aside when hitting some of these same issues.
Fixes #2697
Context
See #2697
Changes Made
new ReuseableStringBuilder
.Testing
Local testing and measurements.
Notes