-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reduce memory allocation in EventSourceEventFormatting (#43947)
- Loading branch information
Showing
2 changed files
with
105 additions
and
42 deletions.
There are no files selected for viewing
135 changes: 93 additions & 42 deletions
135
sdk/core/Azure.Core/src/Shared/EventSourceEventFormatting.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,130 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using Azure.Core.Diagnostics; | ||
using System; | ||
using System.Buffers; | ||
using System.Diagnostics.Tracing; | ||
using System.Globalization; | ||
using System.Linq; | ||
using System.Text; | ||
|
||
#nullable enable | ||
|
||
namespace Azure.Core.Shared | ||
namespace Azure.Core.Shared; | ||
|
||
internal static class EventSourceEventFormatting | ||
{ | ||
internal static class EventSourceEventFormatting | ||
[ThreadStatic] | ||
private static StringBuilder? s_cachedStringBuilder; | ||
private const int CachedStringBuilderCapacity = 512; | ||
|
||
public static string Format(EventWrittenEventArgs eventData) | ||
{ | ||
public static string Format(EventWrittenEventArgs eventData) | ||
{ | ||
var payloadArray = eventData.Payload?.ToArray() ?? Array.Empty<object?>(); | ||
var payloadArray = eventData.Payload?.ToArray() ?? Array.Empty<object?>(); | ||
|
||
ProcessPayloadArray(payloadArray); | ||
ProcessPayloadArray(payloadArray); | ||
|
||
if (eventData.Message != null) | ||
if (eventData.Message != null) | ||
{ | ||
try | ||
{ | ||
try | ||
{ | ||
return string.Format(CultureInfo.InvariantCulture, eventData.Message, payloadArray); | ||
} | ||
catch (FormatException) | ||
{ | ||
} | ||
return string.Format(CultureInfo.InvariantCulture, eventData.Message, payloadArray); | ||
} | ||
catch (FormatException) | ||
{ | ||
} | ||
} | ||
|
||
var stringBuilder = new StringBuilder(); | ||
stringBuilder.Append(eventData.EventName); | ||
StringBuilder stringBuilder = RentStringBuilder(); | ||
stringBuilder.Append(eventData.EventName); | ||
|
||
if (!string.IsNullOrWhiteSpace(eventData.Message)) | ||
{ | ||
stringBuilder.AppendLine(); | ||
stringBuilder.Append(nameof(eventData.Message)).Append(" = ").Append(eventData.Message); | ||
} | ||
|
||
if (!string.IsNullOrWhiteSpace(eventData.Message)) | ||
if (eventData.PayloadNames != null) | ||
{ | ||
for (int i = 0; i < eventData.PayloadNames.Count; i++) | ||
{ | ||
stringBuilder.AppendLine(); | ||
stringBuilder.Append(nameof(eventData.Message)).Append(" = ").Append(eventData.Message); | ||
stringBuilder.Append(eventData.PayloadNames[i]).Append(" = ").Append(payloadArray[i]); | ||
} | ||
} | ||
|
||
if (eventData.PayloadNames != null) | ||
{ | ||
for (int i = 0; i < eventData.PayloadNames.Count; i++) | ||
{ | ||
stringBuilder.AppendLine(); | ||
stringBuilder.Append(eventData.PayloadNames[i]).Append(" = ").Append(payloadArray[i]); | ||
} | ||
} | ||
return ToStringAndReturnStringBuilder(stringBuilder); | ||
} | ||
|
||
return stringBuilder.ToString(); | ||
private static void ProcessPayloadArray(object?[] payloadArray) | ||
{ | ||
for (int i = 0; i < payloadArray.Length; i++) | ||
{ | ||
payloadArray[i] = FormatValue(payloadArray[i]); | ||
} | ||
} | ||
|
||
private static void ProcessPayloadArray(object?[] payloadArray) | ||
private static object? FormatValue(object? o) | ||
{ | ||
if (o is byte[] bytes) | ||
{ | ||
for (int i = 0; i < payloadArray.Length; i++) | ||
#if NET6_0_OR_GREATER | ||
return Convert.ToHexString(bytes); | ||
#else | ||
// Down-level implementation of Convert.ToHexString that uses a | ||
// Span<char> instead of a StringBuilder to avoid allocations. | ||
// The implementation is copied from .NET's HexConverter.ToString | ||
// See https://github.com/dotnet/runtime/blob/acd31754892ab0431ac2c40038f541ffa7168be7/src/libraries/Common/src/System/HexConverter.cs#L180 | ||
// The only modification is that we allow larger stack allocations. | ||
Span<char> result = bytes.Length > 32 ? | ||
new char[bytes.Length * 2] : | ||
stackalloc char[bytes.Length * 2]; | ||
|
||
int pos = 0; | ||
foreach (byte b in bytes) | ||
{ | ||
payloadArray[i] = FormatValue(payloadArray[i]); | ||
ToCharsBuffer(b, result, pos); | ||
pos += 2; | ||
} | ||
} | ||
|
||
private static object? FormatValue(object? o) | ||
{ | ||
if (o is byte[] bytes) | ||
return result.ToString(); | ||
|
||
static void ToCharsBuffer(byte value, Span<char> buffer, int startingIndex) | ||
{ | ||
var stringBuilder = new StringBuilder(); | ||
foreach (byte b in bytes) | ||
{ | ||
stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", b); | ||
} | ||
// An explanation of this algorithm can be found at | ||
// https://github.com/dotnet/runtime/blob/acd31754892ab0431ac2c40038f541ffa7168be7/src/libraries/Common/src/System/HexConverter.cs#L33 | ||
uint difference = ((value & 0xF0U) << 4) + (value & 0x0FU) - 0x8989U; | ||
uint packedResult = (((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U; | ||
|
||
return stringBuilder.ToString(); | ||
buffer[startingIndex + 1] = (char)(packedResult & 0xFF); | ||
buffer[startingIndex] = (char)(packedResult >> 8); | ||
} | ||
#endif | ||
} | ||
|
||
return o; | ||
} | ||
|
||
return o; | ||
private static StringBuilder RentStringBuilder() | ||
{ | ||
StringBuilder? builder = s_cachedStringBuilder; | ||
if (builder is null) | ||
{ | ||
return new StringBuilder(CachedStringBuilderCapacity); | ||
} | ||
|
||
s_cachedStringBuilder = null; | ||
return builder; | ||
} | ||
|
||
private static string ToStringAndReturnStringBuilder(StringBuilder builder) | ||
{ | ||
string result = builder.ToString(); | ||
if (builder.Capacity <= CachedStringBuilderCapacity) | ||
{ | ||
s_cachedStringBuilder = builder.Clear(); | ||
} | ||
|
||
return result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters