Skip to content

Commit dade052

Browse files
authored
Reduce allocations in CSharpVirtualCharService.TryConvertStringToVirtualChars (#79623)
This method is accounting for 1.4% of allocations in the OOP process in a completion scenario in the RazorEditing.CompletionInCohosting speedometer test I am looking at. Instead of doing all the work and hitting the pools, we can just handle the normal case of there not being any surrogates or escape characters and just create a VirtualCharSequence wrapper around the existing string. Locally, I see about 75% of strings that come through hit this optimization. Going to do a test insertion with this change to verify speedometer numbers before elevating out of draft status.
1 parent 7074a21 commit dade052

File tree

1 file changed

+23
-1
lines changed

1 file changed

+23
-1
lines changed

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,14 @@ private static VirtualCharSequence TryConvertStringToVirtualChars(
245245

246246
var startIndexInclusive = startDelimiter.Length;
247247
var endIndexExclusive = tokenText.Length - endDelimiter.Length;
248+
var offset = token.SpanStart;
249+
250+
// Avoid creating and processsing the runes if there are no escapes or surrogates in the string.
251+
if (!ContainsEscapeOrSurrogate(tokenText.AsSpan(startIndexInclusive, endIndexExclusive - startIndexInclusive), escapeBraces))
252+
{
253+
var sequence = VirtualCharSequence.Create(offset, tokenText);
254+
return sequence.GetSubSequence(TextSpan.FromBounds(startIndexInclusive, endIndexExclusive));
255+
}
248256

249257
// Do things in two passes. First, convert everything in the string to a 16-bit-char+span. Then walk
250258
// again, trying to create Runes from the 16-bit-chars. We do this to simplify complex cases where we may
@@ -253,7 +261,6 @@ private static VirtualCharSequence TryConvertStringToVirtualChars(
253261
using var _ = ArrayBuilder<(char ch, TextSpan span)>.GetInstance(out var charResults);
254262

255263
// First pass, just convert everything in the string (i.e. escapes) to plain 16-bit characters.
256-
var offset = token.SpanStart;
257264
for (var index = startIndexInclusive; index < endIndexExclusive;)
258265
{
259266
var ch = tokenText[index];
@@ -282,6 +289,21 @@ private static VirtualCharSequence TryConvertStringToVirtualChars(
282289
return CreateVirtualCharSequence(tokenText, offset, startIndexInclusive, endIndexExclusive, charResults);
283290
}
284291

292+
private static bool ContainsEscapeOrSurrogate(ReadOnlySpan<char> tokenText, bool escapeBraces)
293+
{
294+
foreach (var ch in tokenText)
295+
{
296+
if (ch == '\\')
297+
return true;
298+
else if (escapeBraces && IsOpenOrCloseBrace(ch))
299+
return true;
300+
else if (char.IsSurrogate(ch))
301+
return true;
302+
}
303+
304+
return false;
305+
}
306+
285307
private static VirtualCharSequence CreateVirtualCharSequence(
286308
string tokenText, int offset, int startIndexInclusive, int endIndexExclusive, ArrayBuilder<(char ch, TextSpan span)> charResults)
287309
{

0 commit comments

Comments
 (0)