Browse Source

Merge pull request #8460 from workgroupengineering/features/Core/StringBuilder

feat: StringBuilderCache
flickerRepo
Max Katz 3 years ago
committed by GitHub
parent
commit
d2146ba04e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Avalonia.Base/Avalonia.Base.csproj
  2. 4
      src/Avalonia.Base/Input/KeyGesture.cs
  3. 6
      src/Avalonia.Base/Logging/TraceLogSink.cs
  4. 4
      src/Avalonia.Base/Media/BoxShadow.cs
  5. 6
      src/Avalonia.Base/Media/BoxShadows.cs
  6. 4
      src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs
  7. 4
      src/Avalonia.Base/Media/HslColor.cs
  8. 4
      src/Avalonia.Base/Media/HsvColor.cs
  9. 7
      src/Avalonia.Base/Styling/NthChildSelector.cs
  10. 6
      src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
  11. 6
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  12. 68
      src/Avalonia.Base/Utilities/StringBuilderCache.cs
  13. 1
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  14. 6
      src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs
  15. 8
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  16. 9
      src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs
  17. 6
      src/Avalonia.Controls/Documents/InlineCollection.cs
  18. 5
      src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs
  19. 3
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  20. 6
      src/Windows/Avalonia.Win32/ClipboardFormats.cs
  21. 6
      src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs
  22. 6
      src/Windows/Avalonia.Win32/OleDataObject.cs
  23. 1
      src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj

2
src/Avalonia.Base/Avalonia.Base.csproj

@ -35,8 +35,10 @@
<InternalsVisibleTo Include="Avalonia.Skia.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)"/>
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
</ItemGroup>

4
src/Avalonia.Base/Input/KeyGesture.cs

@ -96,7 +96,7 @@ namespace Avalonia.Input
public override string ToString()
{
var s = new StringBuilder();
var s = StringBuilderCache.Acquire();
static void Plus(StringBuilder s)
{
@ -132,7 +132,7 @@ namespace Avalonia.Input
Plus(s);
s.Append(Key);
return s.ToString();
return StringBuilderCache.GetStringAndRelease(s);
}
public bool Matches(KeyEventArgs keyEvent) =>

6
src/Avalonia.Base/Logging/TraceLogSink.cs

@ -46,7 +46,7 @@ namespace Avalonia.Logging
object? source,
object?[]? values)
{
var result = new StringBuilder(template.Length);
var result = StringBuilderCache.Acquire(template.Length);
var r = new CharacterReader(template.AsSpan());
var i = 0;
@ -89,7 +89,7 @@ namespace Avalonia.Logging
result.Append(')');
}
return result.ToString();
return StringBuilderCache.GetStringAndRelease(result);
}
private static string Format(
@ -98,7 +98,7 @@ namespace Avalonia.Logging
object? source,
object?[] v)
{
var result = new StringBuilder(template.Length);
var result = StringBuilderCache.Acquire(template.Length);
var r = new CharacterReader(template.AsSpan());
var i = 0;

4
src/Avalonia.Base/Media/BoxShadow.cs

@ -80,7 +80,7 @@ namespace Avalonia.Media
public override string ToString()
{
var sb = new StringBuilder();
var sb = StringBuilderCache.Acquire();
if (IsEmpty)
{
@ -114,7 +114,7 @@ namespace Avalonia.Media
sb.AppendFormat(" {0}", Color.ToString());
return sb.ToString();
return StringBuilderCache.GetStringAndRelease(sb);
}
public static unsafe BoxShadow Parse(string s)

6
src/Avalonia.Base/Media/BoxShadows.cs

@ -1,7 +1,7 @@
using System;
using System.ComponentModel;
using System.Text;
using Avalonia.Animation.Animators;
using Avalonia.Utilities;
namespace Avalonia.Media
{
@ -45,7 +45,7 @@ namespace Avalonia.Media
public override string ToString()
{
var sb = new StringBuilder();
var sb = StringBuilderCache.Acquire();
if (Count == 0)
{
@ -57,7 +57,7 @@ namespace Avalonia.Media
sb.AppendFormat("{0} ", boxShadow.ToString());
}
return sb.ToString();
return StringBuilderCache.GetStringAndRelease(sb);
}

4
src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs

@ -77,7 +77,7 @@ namespace Avalonia.Media.Fonts
/// </returns>
public override string ToString()
{
var builder = new StringBuilder();
var builder = StringBuilderCache.Acquire();
for (var index = 0; index < Names.Count; index++)
{
@ -91,7 +91,7 @@ namespace Avalonia.Media.Fonts
builder.Append(", ");
}
return builder.ToString();
return StringBuilderCache.GetStringAndRelease(builder);
}
/// <summary>

4
src/Avalonia.Base/Media/HslColor.cs

@ -202,7 +202,7 @@ namespace Avalonia.Media
/// <inheritdoc/>
public override string ToString()
{
var sb = new StringBuilder();
var sb = StringBuilderCache.Acquire();
// Use a format similar to CSS. However:
// - To ensure precision is never lost, allow decimal places.
@ -225,7 +225,7 @@ namespace Avalonia.Media
sb.Append(A.ToString(CultureInfo.InvariantCulture));
sb.Append(')');
return sb.ToString();
return StringBuilderCache.GetStringAndRelease(sb);
}
/// <summary>

4
src/Avalonia.Base/Media/HsvColor.cs

@ -202,7 +202,7 @@ namespace Avalonia.Media
/// <inheritdoc/>
public override string ToString()
{
var sb = new StringBuilder();
var sb = StringBuilderCache.Acquire();
// Use a format similar to CSS. However:
// - To ensure precision is never lost, allow decimal places.
@ -225,7 +225,7 @@ namespace Avalonia.Media
sb.Append(A.ToString(CultureInfo.InvariantCulture));
sb.Append(')');
return sb.ToString();
return StringBuilderCache.GetStringAndRelease(sb);
}
/// <summary>

7
src/Avalonia.Base/Styling/NthChildSelector.cs

@ -1,8 +1,8 @@
#nullable enable
using System;
using System.Text;
using Avalonia.LogicalTree;
using Avalonia.Styling.Activators;
using Avalonia.Utilities;
namespace Avalonia.Styling
{
@ -110,7 +110,8 @@ namespace Avalonia.Styling
public override string ToString()
{
var expectedCapacity = NthLastChildSelectorName.Length + 8;
var stringBuilder = new StringBuilder(_previous?.ToString(), expectedCapacity);
var stringBuilder = StringBuilderCache.Acquire(expectedCapacity);
stringBuilder.Append(_previous?.ToString());
stringBuilder.Append(':');
stringBuilder.Append(_reversed ? NthLastChildSelectorName : NthChildSelectorName);
@ -140,7 +141,7 @@ namespace Avalonia.Styling
stringBuilder.Append(')');
return stringBuilder.ToString();
return StringBuilderCache.GetStringAndRelease(stringBuilder);
}
}
}

6
src/Avalonia.Base/Styling/PropertyEqualsSelector.cs

@ -1,8 +1,8 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using Avalonia.Styling.Activators;
using Avalonia.Utilities;
#nullable enable
@ -42,7 +42,7 @@ namespace Avalonia.Styling
{
if (_selectorString == null)
{
var builder = new StringBuilder();
var builder = StringBuilderCache.Acquire();
if (_previous != null)
{
@ -67,7 +67,7 @@ namespace Avalonia.Styling
builder.Append(_value ?? string.Empty);
builder.Append(']');
_selectorString = builder.ToString();
_selectorString = StringBuilderCache.GetStringAndRelease(builder);
}
return _selectorString;

6
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Controls;
using Avalonia.Styling.Activators;
using Avalonia.Utilities;
#nullable enable
@ -145,7 +145,7 @@ namespace Avalonia.Styling
private string BuildSelectorString()
{
var builder = new StringBuilder();
var builder = StringBuilderCache.Acquire();
if (_previous != null)
{
@ -185,7 +185,7 @@ namespace Avalonia.Styling
}
}
return builder.ToString();
return StringBuilderCache.GetStringAndRelease(builder);
}
}
}

68
src/Avalonia.Base/Utilities/StringBuilderCache.cs

@ -0,0 +1,68 @@
// This file is imported from dotnet/runtime
// Source Link: https://github.com/dotnet/runtime/blob/e63d21947e734db2da5093510a6636b5b7fb45b5/src/libraries/Common/src/System/Text/StringBuilderCache.cs
// Commit: a9c5ead on Feb 10, 2021, https://github.com/dotnet/runtime/commit/a9c5eadd951dcba73167f72cc624eb790573663a
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Text;
namespace Avalonia.Utilities;
// <summary>Provide a cached reusable instance of stringbuilder per thread.</summary>
internal static class StringBuilderCache
{
// The value 360 was chosen in discussion with performance experts as a compromise between using
// as little memory per thread as possible and still covering a large part of short-lived
// StringBuilder creations on the startup path of VS designers.
internal const int MaxBuilderSize = 360;
private const int DefaultCapacity = 16; // == StringBuilder.DefaultCapacity
// WARNING: We allow diagnostic tools to directly inspect this member (t_cachedInstance).
// See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details.
// Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools.
// Get in touch with the diagnostics team if you have questions.
[ThreadStatic]
private static StringBuilder? t_cachedInstance;
/// <summary>Get a StringBuilder for the specified capacity.</summary>
/// <remarks>If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied.</remarks>
public static StringBuilder Acquire(int capacity = DefaultCapacity)
{
if (capacity <= MaxBuilderSize)
{
StringBuilder? sb = t_cachedInstance;
if (sb != null)
{
// Avoid stringbuilder block fragmentation by getting a new StringBuilder
// when the requested size is larger than the current capacity
if (capacity <= sb.Capacity)
{
t_cachedInstance = null;
sb.Clear();
return sb;
}
}
}
return new StringBuilder(capacity);
}
/// <summary>Place the specified builder in the cache if it is not too big.</summary>
public static void Release(StringBuilder sb)
{
if (sb.Capacity <= MaxBuilderSize)
{
t_cachedInstance = sb;
}
}
/// <summary>ToString() the stringbuilder, Release it to the cache, and return the resulting string.</summary>
public static string GetStringAndRelease(StringBuilder sb)
{
string result = sb.ToString();
Release(sb);
return result;
}
}

1
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -50,6 +50,7 @@
<Compile Include="../Avalonia.Base/Utilities/MathUtilities.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
<Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\ArgumentListParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>

6
src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs

@ -2,7 +2,7 @@
using System.Globalization;
using System.Collections.Generic;
using Avalonia.Media;
using System.Text;
using Avalonia.Utilities;
namespace Avalonia.Controls.Primitives
{
@ -113,7 +113,7 @@ namespace Avalonia.Controls.Primitives
// Cache results for next time as well
if (closestKnownColor != KnownColor.None)
{
StringBuilder sb = new StringBuilder();
var sb = StringBuilderCache.Acquire();
string name = closestKnownColor.ToString();
// Add spaces converting PascalCase to human-readable names
@ -128,7 +128,7 @@ namespace Avalonia.Controls.Primitives
sb.Append(name[i]);
}
string displayName = sb.ToString();
string displayName = StringBuilderCache.GetStringAndRelease(sb);
lock (cacheMutex)
{

8
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -6020,7 +6020,7 @@ namespace Avalonia.Controls
/// <returns>The formatted string.</returns>
private string FormatClipboardContent(DataGridRowClipboardEventArgs e)
{
var text = new StringBuilder();
var text = StringBuilderCache.Acquire();
var clipboardRowContent = e.ClipboardRowContent;
var numberOfItem = clipboardRowContent.Count;
for (int cellIndex = 0; cellIndex < numberOfItem; cellIndex++)
@ -6037,7 +6037,7 @@ namespace Avalonia.Controls
text.Append('\n');
}
}
return text.ToString();
return StringBuilderCache.GetStringAndRelease(text);
}
/// <summary>
@ -6052,7 +6052,7 @@ namespace Avalonia.Controls
if (ctrl && !shift && !alt && ClipboardCopyMode != DataGridClipboardCopyMode.None && SelectedItems.Count > 0)
{
StringBuilder textBuilder = new StringBuilder();
var textBuilder = StringBuilderCache.Acquire();
if (ClipboardCopyMode == DataGridClipboardCopyMode.IncludeHeader)
{
@ -6078,7 +6078,7 @@ namespace Avalonia.Controls
textBuilder.Append(FormatClipboardContent(itemArgs));
}
string text = textBuilder.ToString();
string text = StringBuilderCache.GetStringAndRelease(textBuilder);
if (!string.IsNullOrEmpty(text))
{

9
src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs

@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Data.Converters;
using Avalonia.Input;
using Avalonia.Utilities;
namespace Avalonia.Controls.Converters
{
@ -62,7 +63,7 @@ namespace Avalonia.Controls.Converters
private static string ToString(KeyGesture gesture, string meta)
{
var s = new StringBuilder();
var s = StringBuilderCache.Acquire();
static void Plus(StringBuilder s)
{
@ -98,12 +99,12 @@ namespace Avalonia.Controls.Converters
Plus(s);
s.Append(ToString(gesture.Key));
return s.ToString();
return StringBuilderCache.GetStringAndRelease(s);
}
private static string ToOSXString(KeyGesture gesture)
{
var s = new StringBuilder();
var s = StringBuilderCache.Acquire();
if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Control))
{
@ -127,7 +128,7 @@ namespace Avalonia.Controls.Converters
s.Append(ToOSXString(gesture.Key));
return s.ToString();
return StringBuilderCache.GetStringAndRelease(s);
}
private static string ToString(Key key)

6
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -1,8 +1,8 @@
using System;
using System.Text;
using Avalonia.Collections;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.Utilities;
namespace Avalonia.Controls.Documents
{
@ -70,14 +70,14 @@ namespace Avalonia.Controls.Documents
{
get
{
var builder = new StringBuilder();
var builder = StringBuilderCache.Acquire();
foreach (var inline in this)
{
inline.AppendText(builder);
}
return builder.ToString();
return StringBuilderCache.GetStringAndRelease(builder);
}
}

5
src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs

@ -2,6 +2,7 @@ using System;
using System.Text;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics
@ -10,9 +11,9 @@ namespace Avalonia.Diagnostics
{
public static string PrintVisualTree(IVisual visual)
{
StringBuilder result = new StringBuilder();
var result = StringBuilderCache.Acquire();
PrintVisualTree(visual, result, 0);
return result.ToString();
return StringBuilderCache.GetStringAndRelease(result);
}
private static void PrintVisualTree(IVisual visual, StringBuilder builder, int indent)

3
src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj

@ -7,6 +7,9 @@
<DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
</PropertyGroup>
<Import Project="IncludeXamlIlSre.props" />
<ItemGroup>
<Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
</ItemGroup>

6
src/Windows/Avalonia.Win32/ClipboardFormats.cs

@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using Avalonia.Input;
using Avalonia.Win32.Interop;
using Avalonia.Utilities;
namespace Avalonia.Win32
{
@ -35,9 +35,9 @@ namespace Avalonia.Win32
private static string QueryFormatName(ushort format)
{
StringBuilder sb = new StringBuilder(MAX_FORMAT_NAME_LENGTH);
var sb = StringBuilderCache.Acquire(MAX_FORMAT_NAME_LENGTH);
if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0)
return sb.ToString();
return StringBuilderCache.GetStringAndRelease(sb);
return null;
}

6
src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs

@ -1,6 +1,6 @@
using System.Text;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Utilities;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32.Input
@ -49,7 +49,7 @@ namespace Avalonia.Win32.Input
public string StringFromVirtualKey(uint virtualKey)
{
StringBuilder result = new StringBuilder(256);
var result = StringBuilderCache.Acquire(256);
int length = UnmanagedMethods.ToUnicode(
virtualKey,
0,
@ -57,7 +57,7 @@ namespace Avalonia.Win32.Input
result,
256,
0);
return result.ToString();
return StringBuilderCache.GetStringAndRelease(result);
}
private void UpdateKeyStates()

6
src/Windows/Avalonia.Win32/OleDataObject.cs

@ -6,9 +6,9 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using Avalonia.Input;
using Avalonia.MicroCom;
using Avalonia.Utilities;
using Avalonia.Win32.Interop;
using IDataObject = Avalonia.Input.IDataObject;
@ -103,11 +103,11 @@ namespace Avalonia.Win32
for (int i = 0; i < fileCount; i++)
{
int pathLen = UnmanagedMethods.DragQueryFile(hGlobal, i, null, 0);
StringBuilder sb = new StringBuilder(pathLen+1);
var sb = StringBuilderCache.Acquire(pathLen+1);
if (UnmanagedMethods.DragQueryFile(hGlobal, i, sb, sb.Capacity) == pathLen)
{
files.Add(sb.ToString());
files.Add(StringBuilderCache.GetStringAndRelease(sb));
}
}
}

1
src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj

@ -16,6 +16,7 @@
<ItemGroup>
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerExtensions\**\*.cs" />
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\AvaloniaXamlIlRuntimeCompiler.cs" />
<Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />

Loading…
Cancel
Save