Remove some of usages of StringBuilderCache from System.Runtime.Extensions#30072
Conversation
ViktorHofer
left a comment
There was a problem hiding this comment.
The JIT is pretty good with optimizing stackallocing constant buffer sizes. Because of that I would remove the variable part and always alloc up to a the threshold like 255, as it makes no measurable difference if you alloc 128 or 255 chars.
…nt.Unix.ExpandEnvironmentVariablesCore
| hexOrder[j++] = HexDigit(digit); | ||
| } | ||
| return result; | ||
| return new string(hexOrder); |
There was a problem hiding this comment.
If you know the exact number of chars you can use the new string.Create overload with Span which is the absolute fastest way to build a string.
There was a problem hiding this comment.
Are you talking about
public static string Create<TState>(int length, TState state, SpanAction<char, TState> action) ?
How I can use it — I couldn't find any clue for now...
There was a problem hiding this comment.
From unit test:
char[] input = expected.ToCharArray();
string result = string.Create(input.Length, input, (span, state) =>
{
Assert.Same(input, state);
for (int i = 0; i < state.Length; i++)
{
span[i] = state[i];
}
});
That's really the faster than new string(span) ??
There was a problem hiding this comment.
Yes, that's the fastest way to create a string. For more info around that read the midsection about Span in the Runtime from @stephentoub: https://msdn.microsoft.com/en-us/magazine/mt814808.aspx
There was a problem hiding this comment.
Of course without creating the char array from the unit test... You just need the length, you want to avoid heap allocations where possible
There was a problem hiding this comment.
Changes pushed, thanks for enlightening.
Am I right that we are relying that JIT will optimize out allocation of lambda here?
| result = new string(hexOrder); | ||
| if (sArray == null) return null; | ||
|
|
||
| Span<char> hexOrder = stackalloc char[sArray.Length * 2]; |
There was a problem hiding this comment.
you want to be defensive here to avoid a stack overflow. I don't know what are the usual sizes of the given array is, but a good rule is to limit the stackalloc size to ~255 chars. As already commented below, if you know the fixed size to begin with and it's not too large for allocating on the stack you probably want to use string.Create instead.
There was a problem hiding this comment.
stackalloc removed here
| public override string ToString () | ||
| { | ||
| StringBuilder sb = StringBuilderCache.Acquire(); | ||
| Span<char> charSpan = stackalloc char[255]; |
There was a problem hiding this comment.
What's a typical length of these ApplicationIds? 255 is fine, but if they're all going to be much less than that, it'd be nice to save on the stack space by using 128 or something smaller (but only if we think that the 99% case will fit into that smaller size).
There was a problem hiding this comment.
culture="en-us", version="1.0.0.0", publicKeyToken="43cb1e8e7a352766", processorArchitecture="x86"
has 98 chars, leaving 30 chars for application name. I don't have a statictics for application names, but I met already 38 char exe name in wild. I'm rather go to 255. What's your decision?
There was a problem hiding this comment.
I met already 38 char exe name in wild
As Stephen said, this is about optimizing for the 99% case. I think that it is safe to say that 99% of the .exes out there have name shorter than 38 chars.
There was a problem hiding this comment.
@ViktorHofer you said
The JIT is pretty good with optimizing stackallocing constant buffer sizes
Could you explain? I'm not familiar.
There was a problem hiding this comment.
| result = new string(hexOrder); | ||
| } | ||
| return result; | ||
| }); |
There was a problem hiding this comment.
Nice use of string.Create to avoid the char[] allocation.
There was a problem hiding this comment.
It would be better to append the characters directly to the ValueStringBuilder, and completely avoid the String allocation.
| StringBuilder sb = StringBuilderCache.Acquire(value.Length); | ||
| HtmlEncode(value, index, sb); | ||
| return StringBuilderCache.GetStringAndRelease(sb); | ||
| Span<char> charSpan = stackalloc char[value.Length]; |
There was a problem hiding this comment.
This is dangerous. value could be really big, in which case we'd try to stackalloc that much space and could easily result in a stack overflow / crash. This should instead just using a fixed amount, e.g. 256, or not use stack space at all and just pass an empty span to ValueStringBuilder. Or update ValueStringBuilder with a capacity constructor.
Note, though, that @benjamin-hodgson already has a PR that addresses WebUtility at #27250, so it probably makes sense to just undo these WebUtility.cs changes and let his PR handle that.
There was a problem hiding this comment.
For the sake of this PR I would recommend to just pass a stackalloc char[255] to the ValueStringBuilder and let @benjamin-hodgson fine grain the stack allocation in his PR.
There was a problem hiding this comment.
@ViktorHofer, that makes me nervous, given that these changes showed a regression on throughput in his PR: #27250 (comment).
There was a problem hiding this comment.
I see, that's unfortunate, I didn't know about the regression.
There was a problem hiding this comment.
So, do you like me to undo WebUtility changes?
There was a problem hiding this comment.
Yes, but you also need to undo the removal of the StringBuilderCache from the assembly.
|
For the ApplicationId.ToString() and the Environment.ExpandEnvironmentVariablesCore (Unix) changes we would need perf measurements. Could you please do that? |
| @@ -51,7 +52,7 @@ private static string ExpandEnvironmentVariablesCore(string name) | |||
| } | |||
| result.Append(name.Substring(lastPos)); | |||
There was a problem hiding this comment.
You can replace result.Append(name.Substring will result.Append(name.AsSpan to void unnecessary allocations while you are on it.
There was a problem hiding this comment.
there's still one left in line 53
|
Thanks @leotsarev |
|
Thanks everybody for input. |
| public override string ToString () | ||
| { | ||
| StringBuilder sb = StringBuilderCache.Acquire(); | ||
| Span<char> charSpan = stackalloc char[127]; |
There was a problem hiding this comment.
127 is a weird number. Why not use 128 which spans from 0 - 127?
There was a problem hiding this comment.
The space on the stack is aligned to pointer sized chunks anyway. If you use 127, there will be one unused byte.
128 is better.
ViktorHofer
left a comment
There was a problem hiding this comment.
Perf tests would be nice but not a must for me. Thanks a lot! 👍
|
@ViktorHofer I measured old & new implementation of ExpandEnvironmentVariablesCore (using existing perf test) |
|
Have you measured it on Unix? Note that the implementation you are changing is Unix specific. Could you please share the source to your perf test? |
|
@jkotas no, but I temporary changed code to use Unix-specfic code on Windows |
|
Thank you for your contribution. If you get to the bottom of why the perf tests do not work properly for you, please share the results. But as Victor said we are not really worried about it given the nature of the change. |
…sions (dotnet/corefx#30072) * Remove StringBuilderCache from ApplicationId * Remove StringBuilderCache in favor of ValueStringBuilder in Environment.Unix.ExpandEnvironmentVariablesCore Commit migrated from dotnet/corefx@b987a3b
fixes #29997
That's a work in progress to get initial feedback about removing.
Checklist
ExpandEnvironmentVariablesCore benchmark