From 69d61271f900afd7ae6dea7f0c5e57d43090f9dd Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 17 Nov 2020 05:33:54 -0500 Subject: [PATCH] Avoid string/StringBuilder/char[] allocation in LogValuesFormatter's ctor (#44746) This is producing thousands of allocations at ASP.NET startup, due to almost 1000 call sites to LoggerMessage.Define. --- .../src/LogValuesFormatter.cs | 23 ++++++++++++++----- ...oft.Extensions.Logging.Abstractions.csproj | 8 +++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LogValuesFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LogValuesFormatter.cs index c6322199d6fda..2368a18848465 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LogValuesFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LogValuesFormatter.cs @@ -20,6 +20,10 @@ internal class LogValuesFormatter private readonly string _format; private readonly List _valueNames = new List(); + // NOTE: If this assembly ever builds for netcoreapp, the below code should change to: + // - Be annotated as [SkipLocalsInit] to avoid zero'ing the stackalloc'd char span + // - Format _valueNames.Count directly into a span + public LogValuesFormatter(string format) { if (format == null) @@ -29,18 +33,25 @@ public LogValuesFormatter(string format) OriginalFormat = format; - var sb = new StringBuilder(); + var vsb = new ValueStringBuilder(stackalloc char[256]); int scanIndex = 0; int endIndex = format.Length; while (scanIndex < endIndex) { int openBraceIndex = FindBraceIndex(format, '{', scanIndex, endIndex); + if (scanIndex == 0 && openBraceIndex == endIndex) + { + // No holes found. + _format = format; + return; + } + int closeBraceIndex = FindBraceIndex(format, '}', openBraceIndex, endIndex); if (closeBraceIndex == endIndex) { - sb.Append(format, scanIndex, endIndex - scanIndex); + vsb.Append(format.AsSpan(scanIndex, endIndex - scanIndex)); scanIndex = endIndex; } else @@ -48,16 +59,16 @@ public LogValuesFormatter(string format) // Format item syntax : { index[,alignment][ :formatString] }. int formatDelimiterIndex = FindIndexOfAny(format, FormatDelimiters, openBraceIndex, closeBraceIndex); - sb.Append(format, scanIndex, openBraceIndex - scanIndex + 1); - sb.Append(_valueNames.Count.ToString(CultureInfo.InvariantCulture)); + vsb.Append(format.AsSpan(scanIndex, openBraceIndex - scanIndex + 1)); + vsb.Append(_valueNames.Count.ToString()); _valueNames.Add(format.Substring(openBraceIndex + 1, formatDelimiterIndex - openBraceIndex - 1)); - sb.Append(format, formatDelimiterIndex, closeBraceIndex - formatDelimiterIndex + 1); + vsb.Append(format.AsSpan(formatDelimiterIndex, closeBraceIndex - formatDelimiterIndex + 1)); scanIndex = closeBraceIndex + 1; } } - _format = sb.ToString(); + _format = vsb.ToString(); } public string OriginalFormat { get; private set; } diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj index 26c5d1f3329de..457b3e9b8c7c7 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj @@ -3,6 +3,7 @@ netstandard2.0;net461 true + true @@ -12,6 +13,13 @@ Link="Common\src\Extensions\Logging\NullExternalScopeProvider.cs" /> + + + + + +