Skip to content

Commit e9d4e75

Browse files
perf: replace StringBuilder with ValueListBuilder<char> (#1545)
- Replaces `StringBuilder` with `ValueListBuilder<char>` - Added some extension methods to make this easier - Uses `SkipLocalsInit` to avoid the cost to zero `stackalloc char[]` #### Benchmarks I have no idea why, all the new changes I make are slower in the benchmarks, even when they should be a straight upgrade 🤔 I'm running into this issue a lot with #1542 ### Before | Method | Mean | Error | StdDev | Median | Gen0 | Gen1 | Allocated | |------------------------------ |---------:|--------:|---------:|---------:|----------:|----------:|----------:| | Default_CodeFormatter_Tests | 133.7 ms | 2.65 ms | 3.54 ms | 133.7 ms | 2000.0000 | 1000.0000 | 31.07 MB | | Default_CodeFormatter_Complex | 284.9 ms | 5.60 ms | 13.95 ms | 279.1 ms | 5000.0000 | 2000.0000 | 54.59 MB | ### After | Method | Mean | Error | StdDev | Median | Gen0 | Gen1 | Gen2 | Allocated | |------------------------------ |---------:|--------:|---------:|---------:|----------:|----------:|----------:|----------:| | Default_CodeFormatter_Tests | 136.2 ms | 2.69 ms | 5.74 ms | 134.5 ms | 2000.0000 | 1000.0000 | - | 30.71 MB | | Default_CodeFormatter_Complex | 285.6 ms | 5.71 ms | 14.93 ms | 278.1 ms | 6000.0000 | 3000.0000 | 1000.0000 | 53.45 MB |
1 parent 7b3c080 commit e9d4e75

File tree

6 files changed

+32
-10
lines changed

6 files changed

+32
-10
lines changed

Src/CSharpier.Core/CSharp/SyntaxPrinter/CSharpierIgnore.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using System.Runtime.CompilerServices;
12
using System.Text;
23
using System.Text.RegularExpressions;
34
using CSharpier.Core.DocTypes;
5+
using CSharpier.Core.Utilities;
46
using Microsoft.CodeAnalysis;
57
using Microsoft.CodeAnalysis.CSharp;
68
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -63,21 +65,22 @@ or SyntaxKind.NamespaceDeclaration
6365
&& HasIgnoreComment(syntaxNode);
6466
}
6567

68+
[SkipLocalsInit]
6669
public static List<Doc> PrintNodesRespectingRangeIgnore<T>(
6770
SyntaxList<T> list,
6871
PrintingContext context
6972
)
7073
where T : SyntaxNode
7174
{
7275
var statements = new List<Doc>();
73-
var unFormattedCode = new StringBuilder();
76+
var unFormattedCode = new ValueListBuilder<char>(stackalloc char[64]);
7477
var printUnformatted = false;
7578

7679
foreach (var node in list)
7780
{
7881
if (Token.HasLeadingCommentMatching(node, IgnoreEndRegex))
7982
{
80-
statements.Add(unFormattedCode.ToString().Trim());
83+
statements.Add(unFormattedCode.AsSpan().Trim().ToString());
8184
unFormattedCode.Clear();
8285
printUnformatted = false;
8386
}
@@ -98,9 +101,11 @@ PrintingContext context
98101

99102
if (unFormattedCode.Length > 0)
100103
{
101-
statements.Add(unFormattedCode.ToString().Trim());
104+
statements.Add(unFormattedCode.AsSpan().Trim().ToString());
102105
}
103106

107+
unFormattedCode.Dispose();
108+
104109
return statements;
105110
}
106111

Src/CSharpier.Core/CSharp/SyntaxPrinter/MembersWithForcedLines.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Immutable;
2+
using System.Runtime.CompilerServices;
23
using System.Text;
34
using CSharpier.Core.DocTypes;
45
using CSharpier.Core.Utilities;
@@ -10,6 +11,7 @@ namespace CSharpier.Core.CSharp.SyntaxPrinter;
1011

1112
internal static class MembersWithForcedLines
1213
{
14+
[SkipLocalsInit]
1315
public static List<Doc> Print<T>(
1416
CSharpSyntaxNode node,
1517
IReadOnlyList<T> members,
@@ -24,7 +26,7 @@ public static List<Doc> Print<T>(
2426
result.Add(Doc.HardLine);
2527
}
2628

27-
var unFormattedCode = new StringBuilder();
29+
var unFormattedCode = new ValueListBuilder<char>(stackalloc char[64]);
2830
var printUnformatted = false;
2931
var lastMemberForcedBlankLine = false;
3032
for (var memberIndex = 0; memberIndex < members.Count; memberIndex++)
@@ -35,7 +37,7 @@ public static List<Doc> Print<T>(
3537
if (Token.HasLeadingCommentMatching(member, CSharpierIgnore.IgnoreEndRegex))
3638
{
3739
skipAddingLineBecauseIgnoreEnded = true;
38-
result.Add(unFormattedCode.ToString().Trim());
40+
result.Add(unFormattedCode.AsSpan().Trim().ToString());
3941
unFormattedCode.Clear();
4042
printUnformatted = false;
4143
}
@@ -229,9 +231,11 @@ or SyntaxKind.EndRegionDirectiveTrivia
229231

230232
if (unFormattedCode.Length > 0)
231233
{
232-
result.Add(unFormattedCode.ToString().Trim());
234+
result.Add(unFormattedCode.AsSpan().ToString().Trim());
233235
}
234236

237+
unFormattedCode.Dispose();
238+
235239
return result;
236240
}
237241
}

Src/CSharpier.Core/CSharp/SyntaxPrinter/SeparatedSyntaxList.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Runtime.CompilerServices;
12
using System.Text;
23
using CSharpier.Core.DocTypes;
34
using CSharpier.Core.Utilities;
@@ -34,6 +35,7 @@ public static Doc PrintWithTrailingComma<T>(
3435
// the names above aren't totally accurate
3536
// sometimes there are trailing commas with calls to Print (some patterns do that)
3637
// and if you pass null to PrintWithTrailingComma it won't add a trailing comma if there isn't one
38+
[SkipLocalsInit]
3739
private static Doc Print<T>(
3840
in SeparatedSyntaxList<T> list,
3941
Func<T, PrintingContext, Doc> printFunc,
@@ -48,15 +50,15 @@ private static Doc Print<T>(
4850
list.Count <= 3
4951
? new ValueListBuilder<Doc>([null, null, null, null, null, null, null, null])
5052
: new ValueListBuilder<Doc>(list.Count * 3);
51-
var unFormattedCode = new StringBuilder();
53+
var unFormattedCode = new ValueListBuilder<char>(stackalloc char[64]);
5254
var printUnformatted = false;
5355
for (var x = startingIndex; x < list.Count; x++)
5456
{
5557
var member = list[x];
5658

5759
if (Token.HasLeadingCommentMatching(member, CSharpierIgnore.IgnoreEndRegex))
5860
{
59-
docs.Append(unFormattedCode.ToString().Trim());
61+
docs.Append(unFormattedCode.AsSpan().Trim().ToString());
6062
unFormattedCode.Clear();
6163
printUnformatted = false;
6264
}
@@ -74,7 +76,8 @@ private static Doc Print<T>(
7476
unFormattedCode.Append(CSharpierIgnore.PrintWithoutFormatting(member, context));
7577
if (x < list.SeparatorCount)
7678
{
77-
unFormattedCode.AppendLine(list.GetSeparator(x).Text);
79+
unFormattedCode.Append(list.GetSeparator(x).Text);
80+
unFormattedCode.Append(Environment.NewLine);
7881
}
7982

8083
continue;
@@ -144,10 +147,11 @@ private static Doc Print<T>(
144147

145148
if (unFormattedCode.Length > 0)
146149
{
147-
docs.Append(unFormattedCode.ToString().Trim());
150+
docs.Append(unFormattedCode.AsSpan().Trim().ToString());
148151
}
149152

150153
var output = Doc.Concat(ref docs);
154+
unFormattedCode.Dispose();
151155
docs.Dispose();
152156

153157
return output;

Src/CSharpier.Core/CSharpier.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<SignAssembly>True</SignAssembly>
99
<PublicKey>002400000480000094000000060200000024000052534131000400000100010049d266ea1aeae09c0abfce28b8728314d4e4807126ee8bc56155a7ddc765997ed3522908b469ae133fc49ef0bfa957df36082c1c2e0ec8cdc05a4ca4dbd4e1bea6c17fc1008555e15af13a8fc871a04ffc38f5e60e6203bfaf01d16a2a283b90572ade79135801c1675bf38b7a5a60ec8353069796eb53a26ffdddc9ee1273be</PublicKey>
1010
<LangVersion>13</LangVersion>
11+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1112
</PropertyGroup>
1213
<ItemGroup>
1314
<PackageReference Include="LovettSoftware.XmlDiff" />

Src/CSharpier.Core/Utilities/StringBuilderExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Globalization;
2+
using System.Runtime.CompilerServices;
23
using System.Text;
34

45
namespace CSharpier.Core.Utilities;
@@ -21,6 +22,10 @@ public static void Append(this StringBuilder value, CultureInfo cultureInfo, str
2122
}
2223
#endif
2324

25+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
26+
public static void Append(this ref ValueListBuilder<char> builder, string text) =>
27+
builder.Append(text.AsSpan());
28+
2429
public static void TrimStart(this StringBuilder value, params ReadOnlySpan<char> trimChars)
2530
{
2631
int startIndex = 0;

Src/CSharpier.Core/Utilities/ValueListBuilder.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ public ref T this[int index]
4141
}
4242
}
4343

44+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
45+
public void Clear() => Length = 0;
46+
4447
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4548
public void Append(T item)
4649
{

0 commit comments

Comments
 (0)