Reduce allocations in TraceActivity.Name

Introduces StringBuilderCache
This commit is contained in:
Paul Harrington
2016-04-15 16:02:44 -07:00
parent 7dcb9fcc4c
commit f07fcc3000
3 changed files with 135 additions and 5 deletions
+1
View File
@@ -123,6 +123,7 @@
<Compile Include="TraceLog.cs" />
<Compile Include="Utilities\command.cs" />
<Compile Include="TraceUtilities\HistoryDictionary.cs" />
<Compile Include="Utilities\StringBuilderCache.cs" />
<Compile Include="WPPTraceEventParser.cs" />
<Compile Include="ZippedETL.cs" />
<Compile Include="_README.cs" />
+48 -5
View File
@@ -8548,6 +8548,32 @@ namespace Microsoft.Diagnostics.Tracing.Etlx
FxTimer = 34, // FxTransfer + kind(1)
}
private static string ActivityKindToString(ActivityKind kind)
{
switch (kind)
{
case ActivityKind.Invalid: return nameof(ActivityKind.Invalid);
case ActivityKind.Initial: return nameof(ActivityKind.Initial);
case ActivityKind.TaskScheduled: return nameof(ActivityKind.TaskScheduled);
case ActivityKind.TaskStarted: return nameof(ActivityKind.TaskStarted);
case ActivityKind.AwaitTaskScheduled: return nameof(ActivityKind.AwaitTaskScheduled);
case ActivityKind.ClrThreadStart: return nameof(ActivityKind.ClrThreadStart);
case ActivityKind.ClrThreadPool: return nameof(ActivityKind.ClrThreadPool);
case ActivityKind.ClrIOThreadPool: return nameof(ActivityKind.ClrIOThreadPool);
case ActivityKind.FxThreadPool: return nameof(ActivityKind.FxThreadPool);
case ActivityKind.FxTransfer: return nameof(ActivityKind.FxTransfer);
case ActivityKind.FxAsyncIO: return nameof(ActivityKind.FxAsyncIO);
case ActivityKind.FxWinRTDispatch: return nameof(ActivityKind.FxWinRTDispatch);
case ActivityKind.Implied: return nameof(ActivityKind.Implied);
case ActivityKind.TaskWait: return nameof(ActivityKind.TaskWait);
case ActivityKind.TaskWaitSynchronous: return nameof(ActivityKind.TaskWaitSynchronous);
case ActivityKind.FxTimer: return nameof(ActivityKind.FxTimer);
default:
Debug.Fail("Missing ActivityKind case statement.");
return kind.ToString();
}
}
/// <summary>A trace-wide unique id identifying an activity</summary>
public ActivityIndex Index { get { return activityIndex; } }
/// <summary>The activity that initiated or caused the current one</summary>
@@ -8598,12 +8624,29 @@ namespace Microsoft.Diagnostics.Tracing.Etlx
{
get
{
string name = IsThreadActivity ? "ThreadActivity" : ((rawID >> 32) & 1) != 0 ? "Activity (concurrent)" : "Activity (continuation)";
if (Thread == null)
return string.Format("<{0} Index=\"{1}\" RawID=\"0x{2:x}\"/>", name, (int)Index, rawID);
// PERF: Hand-optimized string.Format to minimize allocations
var sb = StringBuilderCache.Acquire();
sb.Append('<');
sb.Append(IsThreadActivity ? "ThreadActivity" : ((rawID >> 32) & 1) != 0 ? "Activity (concurrent)" : "Activity (continuation)");
sb.Append(" Index=\"");
sb.Append((int)Index);
return string.Format("<{0} Index=\"{1}\" Thread=\"{2}\" Create=\"{3:f3}\" Start=\"{4:f3}\" kind=\"{5}\" RawID=\"0x{6:x}\"/>",
name, (int)Index, Thread.VerboseThreadName, CreationTimeRelativeMSec, StartTimeRelativeMSec, kind, rawID);
if (Thread != null)
{
sb.Append("\" Thread=\"");
sb.Append(Thread.VerboseThreadName);
sb.Append("\" Create=\"");
sb.Append(CreationTimeRelativeMSec.ToString("f3"));
sb.Append("\" Start=\"");
sb.Append(StartTimeRelativeMSec.ToString("f3"));
sb.Append("\" kind=\"");
sb.Append(ActivityKindToString(kind));
}
sb.Append("\" RawID=\"0x");
sb.Append(rawID.ToString("x"));
sb.Append("\"/>");
return StringBuilderCache.GetStringAndRelease(sb);
}
}
@@ -0,0 +1,86 @@
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*============================================================
**
** Class: StringBuilderCache
**
** Purpose: provide a cached reusable instance of stringbuilder
** per thread it's an optimisation that reduces the
** number of instances constructed and collected.
**
** Acquire - is used to get a string builder to use of a
** particular size. It can be called any number of
** times, if a stringbuilder is in the cache then
** it will be returned and the cache emptied.
** subsequent calls will return a new stringbuilder.
**
** A StringBuilder instance is cached in
** Thread Local Storage and so there is one per thread
**
** Release - Place the specified builder in the cache if it is
** not too big.
** The stringbuilder should not be used after it has
** been released.
** Unbalanced Releases are perfectly acceptable. It
** will merely cause the runtime to create a new
** stringbuilder next time Acquire is called.
**
** GetStringAndRelease
** - ToString() the stringbuilder, Release it to the
** cache and return the resulting string
**
===========================================================*/
using System;
using System.Text;
namespace Microsoft.Diagnostics.Tracing.Utilities
{
internal static class StringBuilderCache
{
// The value 360 was chosen in discussion with performance experts as a compromise between using
// as litle memory (per thread) as possible and still covering a large part of short-lived
// StringBuilder creations on the startup path of VS designers.
private const int MAX_BUILDER_SIZE = 360;
[ThreadStatic]
private static StringBuilder CachedInstance;
public static StringBuilder Acquire(int capacity = 16 /*StringBuilder.DefaultCapacity*/)
{
if (capacity <= MAX_BUILDER_SIZE)
{
StringBuilder sb = StringBuilderCache.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)
{
StringBuilderCache.CachedInstance = null;
sb.Clear();
return sb;
}
}
}
return new StringBuilder(capacity);
}
public static void Release(StringBuilder sb)
{
if (sb.Capacity <= MAX_BUILDER_SIZE)
{
StringBuilderCache.CachedInstance = sb;
}
}
public static string GetStringAndRelease(StringBuilder sb)
{
string result = sb.ToString();
Release(sb);
return result;
}
}
}