Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/ThirdParty/MSBuild/ReusableStringBuilder.cs
jonathan adamczewski f270855eef AutomationTool: Compile script modules within the application
Add a layer of caching to avoid running msbuild as much as possible.

#jira UE-109181
#rb ben.marsh

#ROBOMERGE-SOURCE: CL 17102399 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v853-17066230)

[CL 17102408 by jonathan adamczewski in ue5-release-engine-test branch]
2021-08-09 10:39:35 -04:00

314 lines
10 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using System.Threading;
namespace Microsoft.Build.Shared
{
/// <summary>
/// A StringBuilder lookalike that reuses its internal storage.
/// </summary>
/// <remarks>
/// This class is being deprecated in favor of SpanBasedStringBuilder in StringTools. Avoid adding more uses.
/// </remarks>
internal sealed class ReuseableStringBuilder : IDisposable
{
/// <summary>
/// Captured string builder.
/// </summary>
private StringBuilder _borrowedBuilder;
/// <summary>
/// Capacity to initialize the builder with.
/// </summary>
private int _capacity;
/// <summary>
/// Create a new builder, under the covers wrapping a reused one.
/// </summary>
internal ReuseableStringBuilder(int capacity = 16) // StringBuilder default is 16
{
_capacity = capacity;
// lazy initialization of the builder
}
/// <summary>
/// The length of the target.
/// </summary>
public int Length
{
get { return (_borrowedBuilder == null) ? 0 : _borrowedBuilder.Length; }
set
{
LazyPrepare();
_borrowedBuilder.Length = value;
}
}
/// <summary>
/// Convert to a string.
/// </summary>
public override string ToString()
{
if (_borrowedBuilder == null)
{
return String.Empty;
}
return _borrowedBuilder.ToString();
}
/// <summary>
/// Dispose, indicating you are done with this builder.
/// </summary>
void IDisposable.Dispose()
{
if (_borrowedBuilder != null)
{
ReuseableStringBuilderFactory.Release(_borrowedBuilder);
_borrowedBuilder = null;
_capacity = -1;
}
}
/// <summary>
/// Append a character.
/// </summary>
internal ReuseableStringBuilder Append(char value)
{
LazyPrepare();
_borrowedBuilder.Append(value);
return this;
}
/// <summary>
/// Append a string.
/// </summary>
internal ReuseableStringBuilder Append(string value)
{
LazyPrepare();
_borrowedBuilder.Append(value);
return this;
}
/// <summary>
/// Append a substring.
/// </summary>
internal ReuseableStringBuilder Append(string value, int startIndex, int count)
{
LazyPrepare();
_borrowedBuilder.Append(value, startIndex, count);
return this;
}
public ReuseableStringBuilder AppendSeparated(char separator, ICollection<string> strings)
{
LazyPrepare();
var separatorsRemaining = strings.Count - 1;
foreach (var s in strings)
{
_borrowedBuilder.Append(s);
if (separatorsRemaining > 0)
{
_borrowedBuilder.Append(separator);
}
separatorsRemaining--;
}
return this;
}
public ReuseableStringBuilder Clear()
{
LazyPrepare();
_borrowedBuilder.Clear();
return this;
}
/// <summary>
/// Remove a substring.
/// </summary>
internal ReuseableStringBuilder Remove(int startIndex, int length)
{
LazyPrepare();
_borrowedBuilder.Remove(startIndex, length);
return this;
}
/// <summary>
/// Grab a backing builder if necessary.
/// </summary>
private void LazyPrepare()
{
if (_borrowedBuilder == null)
{
ErrorUtilities.VerifyThrow(_capacity != -1, "Reusing after dispose");
_borrowedBuilder = ReuseableStringBuilderFactory.Get(_capacity);
}
}
/// <summary>
/// A utility class that mediates access to a shared string builder.
/// </summary>
/// <remarks>
/// If this shared builder is highly contended, this class could add
/// a second one and try both in turn.
/// </remarks>
private static class ReuseableStringBuilderFactory
{
/// <summary>
/// Made up limit beyond which we won't share the builder
/// because we could otherwise hold a huge builder indefinitely.
/// This size seems reasonable for MSBuild uses (mostly expression expansion)
/// </summary>
private const int MaxBuilderSize = 1024;
/// <summary>
/// The shared builder.
/// </summary>
private static StringBuilder s_sharedBuilder;
#if DEBUG
/// <summary>
/// Count of successful reuses
/// </summary>
private static int s_hits = 0;
/// <summary>
/// Count of failed reuses - a new builder was created
/// </summary>
private static int s_misses = 0;
/// <summary>
/// Count of times the builder capacity was raised to satisfy the caller's request
/// </summary>
private static int s_upsizes = 0;
/// <summary>
/// Count of times the returned builder was discarded because it was too large
/// </summary>
private static int s_discards = 0;
/// <summary>
/// Count of times the builder was returned.
/// </summary>
private static int s_accepts = 0;
/// <summary>
/// Aggregate capacity saved (aggregate midpoints of requested and returned)
/// </summary>
private static int s_saved = 0;
/// <summary>
/// Callstacks of those handed out and not returned yet
/// </summary>
private static ConcurrentDictionary<StringBuilder, string> s_handouts = new ConcurrentDictionary<StringBuilder, string>();
#endif
/// <summary>
/// Obtains a string builder which may or may not already
/// have been used.
/// Never returns null.
/// </summary>
internal static StringBuilder Get(int capacity)
{
#if DEBUG
bool missed = false;
#endif
var returned = Interlocked.Exchange(ref s_sharedBuilder, null);
if (returned == null)
{
#if DEBUG
missed = true;
Interlocked.Increment(ref s_misses);
#endif
// Currently loaned out so return a new one
returned = new StringBuilder(capacity);
}
else if (returned.Capacity < capacity)
{
#if DEBUG
Interlocked.Increment(ref s_upsizes);
#endif
// It's essential we guarantee the capacity because this
// may be used as a buffer to a PInvoke call.
returned.Capacity = capacity;
}
#if DEBUG
Interlocked.Increment(ref s_hits);
if (!missed)
{
Interlocked.Add(ref s_saved, (capacity + returned.Capacity) / 2);
}
// handouts.TryAdd(returned, Environment.StackTrace);
#endif
return returned;
}
/// <summary>
/// Returns the shared builder for the next caller to use.
/// ** CALLERS, DO NOT USE THE BUILDER AFTER RELEASING IT HERE! **
/// </summary>
internal static void Release(StringBuilder returningBuilder)
{
// It's possible for someone to cause the builder to
// enlarge to such an extent that this static field
// would be a leak. To avoid that, only accept
// the builder if it's no more than a certain size.
//
// If some code has a bug and forgets to return their builder
// (or we refuse it here because it's too big) the next user will
// get given a new one, and then return it soon after.
// So the shared builder will be "replaced".
if (returningBuilder.Capacity < MaxBuilderSize)
{
// ErrorUtilities.VerifyThrow(handouts.TryRemove(returningBuilder, out dummy), "returned but not loaned");
returningBuilder.Clear(); // Clear before pooling
Interlocked.Exchange(ref s_sharedBuilder, returningBuilder);
#if DEBUG
Interlocked.Increment(ref s_accepts);
}
else
{
Interlocked.Increment(ref s_discards);
#endif
}
}
#if DEBUG
/// <summary>
/// Debugging dumping
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Handy helper method that can be used to annotate ReuseableStringBuilder when debugging it, but is not hooked up usually for the sake of perf.")]
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.String.Format(System.IFormatProvider,System.String,System.Object[])", Justification = "Handy string that can be used to annotate ReuseableStringBuilder when debugging it, but is not hooked up usually.")]
internal static void DumpUnreturned()
{
String.Format(CultureInfo.CurrentUICulture, "{0} Hits of which\n {1} Misses (was on loan)\n {2} Upsizes (needed bigger) \n\n{3} Returns=\n{4} Discards (returned too large)+\n {5} Accepts\n\n{6} estimated bytes saved", s_hits, s_misses, s_upsizes, s_discards + s_accepts, s_discards, s_accepts, s_saved);
Console.WriteLine("Unreturned string builders were allocated here:");
foreach (var entry in s_handouts.Values)
{
Console.WriteLine(entry + "\n");
}
}
#endif
}
}
}