Skip to content

Commit bb70efc

Browse files
committed
Fix serialization allocation in .NET Framework and < .NET Core 3.1 (#7884)
## Summary of changes - Fixes "incorrect" generated code from `TagsList` - Removes significant additional overhead during serialization - "Fix" vendored System.Buffers code to avoid the same issue ## Reason for change The current generated code for `TagsList` produces something like this: ```csharp private static ReadOnlySpan<byte> DbTypeBytes => new byte[] { 167, 100, 98, 46, 116, 121, 112, 101 }; ``` This _looks_ like it's allocating a new `byte[]` with every invocation, but the compiler actually optimizes this away to be completely zero-allocation, by embedding the array as part of the dll, and then simply returning a `ReadOnlySpan` wrapper pointing to this fixed data. You can see this if you look at the generated IL: ``` .method private hidebysig static specialname valuetype [System.Runtime]System.ReadOnlySpan`1<unsigned int8> get_DbTypeBytes() cil managed { .maxstack 8 // [20 58 - 20 109] IL_0000: ldsflda int64 '<PrivateImplementationDetails>'::A06A154BE3B860D0B56FA96C93523B732045BA0BCE2FFD4769109575CF1953BF IL_0005: ldc.i4.8 IL_0006: newobj instance void valuetype [System.Runtime]System.ReadOnlySpan`1<unsigned int8>::.ctor(void*, int32) IL_000b: ret } // end of method SqlTags::get_DbTypeBytes ``` However, in .NET Framework, even though we have vendored `ReadOnlySpan<T>` so we can get some of the benefits (mostly cleaner code), we _don't_ get these benefits. Which means that the above code _does_ generate a new array with every invocation: ``` .method private hidebysig static specialname valuetype Datadog.Trace.VendoredMicrosoftCode.System.ReadOnlySpan`1<unsigned int8> get_DbTypeBytes() cil managed { .maxstack 8 // [20 58 - 20 109] IL_0000: ldc.i4.8 IL_0001: newarr [netstandard]System.Byte IL_0006: dup IL_0007: ldtoken field int64 '<PrivateImplementationDetails>'::A06A154BE3B860D0B56FA96C93523B732045BA0BCE2FFD4769109575CF1953BF IL_000c: call void [netstandard]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [netstandard]System.Array, valuetype [netstandard]System.RuntimeFieldHandle) IL_0011: call valuetype Datadog.Trace.VendoredMicrosoftCode.System.ReadOnlySpan`1<!0/*unsigned int8*/> valuetype Datadog.Trace.VendoredMicrosoftCode.System.ReadOnlySpan`1<unsigned int8>::op_Implicit(!0/*unsigned int8*/[]) IL_0016: ret } // end of method SqlTags::get_DbTypeBytes ``` This is... Bad 😅 And it explains the _significant_ serialization overhead identified in #7882 for .NET Framework. I also confirmed this applies to all <.NET Core 3.1 too (because we compile for .NET Standard) | Method | Runtime | Mean | Allocated | Alloc Ratio | | -------------------------- | -------------------- | -------: | --------: | ----------: | | WriteEnrichedTraces_Before | .NET 6.0 | 488.9 us | 110 B | 0.001 | | WriteEnrichedTraces_Before | .NET Framework 4.7.2 | 703.3 us | 112537 B | 1.000 | | | | | | | | WriteEnrichedTraces_After | .NET 6.0 | 469.1 us | 105 B | 0.50 | | WriteEnrichedTraces_After | .NET Framework 4.7.2 | 703.4 us | 208 B | 1.00 | ## Implementation details The fix is to just do what we were doing before #5298 introduced this regression 😄 i.e. generate code like this: ```csharp #if NETCOREAPP private static ReadOnlySpan<byte> DbTypeBytes => new byte[] { 167, 100, 98, 46, 116, 121, 112, 101 }; #else private static readonly byte[] DbTypeBytes = new byte[] { 167, 100, 98, 46, 116, 121, 112, 101 #endif ``` ## Test coverage This is all covered by existing tests, and the new benchmark shows the improvment ## Other details I found a couple of other places in the vendored code that has the same issue, and fixed them directly in the code. However, this is not ideal, as if we re-vendor, we'll clobber these updates, so we'll need to update the vendoring code too
1 parent 7b88380 commit bb70efc

File tree

244 files changed

+5677
-10
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

244 files changed

+5677
-10
lines changed

tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/Sources.cs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,24 @@ partial class ")
8888
.Append(@""");");
8989

9090
sb.Append(
91-
@"
91+
@"
92+
#if NETCOREAPP
9293
private static ReadOnlySpan<byte> ")
93-
.Append(property.PropertyName)
94-
.Append(@"Bytes => new byte[] { ")
95-
.Append(tagByteArray)
96-
.Append(@" };");
94+
.Append(property.PropertyName)
95+
.Append(@"Bytes => new byte[] { ")
96+
.Append(tagByteArray)
97+
.Append(@" };")
98+
.Append(
99+
@"
100+
#else
101+
private static readonly byte[] ")
102+
.Append(property.PropertyName)
103+
.Append(@"Bytes = new byte[] { ")
104+
.Append(tagByteArray)
105+
.Append(@" };")
106+
.Append(
107+
@"
108+
#endif");
97109
}
98110
}
99111

@@ -112,12 +124,24 @@ partial class ")
112124
.Append(@""");");
113125

114126
sb.Append(
115-
@"
127+
@"
128+
#if NETCOREAPP
116129
private static ReadOnlySpan<byte> ")
117-
.Append(property.PropertyName)
118-
.Append(@"Bytes => new byte[] { ")
119-
.Append(tagByteArray)
120-
.Append(@" };");
130+
.Append(property.PropertyName)
131+
.Append(@"Bytes => new byte[] { ")
132+
.Append(tagByteArray)
133+
.Append(@" };")
134+
.Append(
135+
@"
136+
#else
137+
private static readonly byte[] ")
138+
.Append(property.PropertyName)
139+
.Append(@"Bytes = new byte[] { ")
140+
.Append(tagByteArray)
141+
.Append(@" };")
142+
.Append(
143+
@"
144+
#endif");
121145
}
122146

123147
sb.Append(

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AerospikeTags.g.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,41 @@ namespace Datadog.Trace.Tagging
1515
partial class AerospikeTags
1616
{
1717
// SpanKindBytes = MessagePack.Serialize("span.kind");
18+
#if NETCOREAPP
1819
private static ReadOnlySpan<byte> SpanKindBytes => new byte[] { 169, 115, 112, 97, 110, 46, 107, 105, 110, 100 };
20+
#else
21+
private static readonly byte[] SpanKindBytes = new byte[] { 169, 115, 112, 97, 110, 46, 107, 105, 110, 100 };
22+
#endif
1923
// InstrumentationNameBytes = MessagePack.Serialize("component");
24+
#if NETCOREAPP
2025
private static ReadOnlySpan<byte> InstrumentationNameBytes => new byte[] { 169, 99, 111, 109, 112, 111, 110, 101, 110, 116 };
26+
#else
27+
private static readonly byte[] InstrumentationNameBytes = new byte[] { 169, 99, 111, 109, 112, 111, 110, 101, 110, 116 };
28+
#endif
2129
// KeyBytes = MessagePack.Serialize("aerospike.key");
30+
#if NETCOREAPP
2231
private static ReadOnlySpan<byte> KeyBytes => new byte[] { 173, 97, 101, 114, 111, 115, 112, 105, 107, 101, 46, 107, 101, 121 };
32+
#else
33+
private static readonly byte[] KeyBytes = new byte[] { 173, 97, 101, 114, 111, 115, 112, 105, 107, 101, 46, 107, 101, 121 };
34+
#endif
2335
// NamespaceBytes = MessagePack.Serialize("aerospike.namespace");
36+
#if NETCOREAPP
2437
private static ReadOnlySpan<byte> NamespaceBytes => new byte[] { 179, 97, 101, 114, 111, 115, 112, 105, 107, 101, 46, 110, 97, 109, 101, 115, 112, 97, 99, 101 };
38+
#else
39+
private static readonly byte[] NamespaceBytes = new byte[] { 179, 97, 101, 114, 111, 115, 112, 105, 107, 101, 46, 110, 97, 109, 101, 115, 112, 97, 99, 101 };
40+
#endif
2541
// SetNameBytes = MessagePack.Serialize("aerospike.setname");
42+
#if NETCOREAPP
2643
private static ReadOnlySpan<byte> SetNameBytes => new byte[] { 177, 97, 101, 114, 111, 115, 112, 105, 107, 101, 46, 115, 101, 116, 110, 97, 109, 101 };
44+
#else
45+
private static readonly byte[] SetNameBytes = new byte[] { 177, 97, 101, 114, 111, 115, 112, 105, 107, 101, 46, 115, 101, 116, 110, 97, 109, 101 };
46+
#endif
2747
// UserKeyBytes = MessagePack.Serialize("aerospike.userkey");
48+
#if NETCOREAPP
2849
private static ReadOnlySpan<byte> UserKeyBytes => new byte[] { 177, 97, 101, 114, 111, 115, 112, 105, 107, 101, 46, 117, 115, 101, 114, 107, 101, 121 };
50+
#else
51+
private static readonly byte[] UserKeyBytes = new byte[] { 177, 97, 101, 114, 111, 115, 112, 105, 107, 101, 46, 117, 115, 101, 114, 107, 101, 121 };
52+
#endif
2953

3054
public override string? GetTag(string key)
3155
{

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AerospikeV1Tags.g.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@ namespace Datadog.Trace.Tagging
1515
partial class AerospikeV1Tags
1616
{
1717
// PeerServiceBytes = MessagePack.Serialize("peer.service");
18+
#if NETCOREAPP
1819
private static ReadOnlySpan<byte> PeerServiceBytes => new byte[] { 172, 112, 101, 101, 114, 46, 115, 101, 114, 118, 105, 99, 101 };
20+
#else
21+
private static readonly byte[] PeerServiceBytes = new byte[] { 172, 112, 101, 101, 114, 46, 115, 101, 114, 118, 105, 99, 101 };
22+
#endif
1923
// PeerServiceSourceBytes = MessagePack.Serialize("_dd.peer.service.source");
24+
#if NETCOREAPP
2025
private static ReadOnlySpan<byte> PeerServiceSourceBytes => new byte[] { 183, 95, 100, 100, 46, 112, 101, 101, 114, 46, 115, 101, 114, 118, 105, 99, 101, 46, 115, 111, 117, 114, 99, 101 };
26+
#else
27+
private static readonly byte[] PeerServiceSourceBytes = new byte[] { 183, 95, 100, 100, 46, 112, 101, 101, 114, 46, 115, 101, 114, 118, 105, 99, 101, 46, 115, 111, 117, 114, 99, 101 };
28+
#endif
2129

2230
public override string? GetTag(string key)
2331
{

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AspNetCoreEndpointTags.g.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ namespace Datadog.Trace.Tagging
1515
partial class AspNetCoreEndpointTags
1616
{
1717
// AspNetCoreEndpointBytes = MessagePack.Serialize("aspnet_core.endpoint");
18+
#if NETCOREAPP
1819
private static ReadOnlySpan<byte> AspNetCoreEndpointBytes => new byte[] { 180, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 101, 110, 100, 112, 111, 105, 110, 116 };
20+
#else
21+
private static readonly byte[] AspNetCoreEndpointBytes = new byte[] { 180, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 101, 110, 100, 112, 111, 105, 110, 116 };
22+
#endif
1923

2024
public override string? GetTag(string key)
2125
{

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AspNetCoreMvcTags.g.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,29 @@ namespace Datadog.Trace.Tagging
1515
partial class AspNetCoreMvcTags
1616
{
1717
// AspNetCoreControllerBytes = MessagePack.Serialize("aspnet_core.controller");
18+
#if NETCOREAPP
1819
private static ReadOnlySpan<byte> AspNetCoreControllerBytes => new byte[] { 182, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 99, 111, 110, 116, 114, 111, 108, 108, 101, 114 };
20+
#else
21+
private static readonly byte[] AspNetCoreControllerBytes = new byte[] { 182, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 99, 111, 110, 116, 114, 111, 108, 108, 101, 114 };
22+
#endif
1923
// AspNetCoreActionBytes = MessagePack.Serialize("aspnet_core.action");
24+
#if NETCOREAPP
2025
private static ReadOnlySpan<byte> AspNetCoreActionBytes => new byte[] { 178, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 97, 99, 116, 105, 111, 110 };
26+
#else
27+
private static readonly byte[] AspNetCoreActionBytes = new byte[] { 178, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 97, 99, 116, 105, 111, 110 };
28+
#endif
2129
// AspNetCoreAreaBytes = MessagePack.Serialize("aspnet_core.area");
30+
#if NETCOREAPP
2231
private static ReadOnlySpan<byte> AspNetCoreAreaBytes => new byte[] { 176, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 97, 114, 101, 97 };
32+
#else
33+
private static readonly byte[] AspNetCoreAreaBytes = new byte[] { 176, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 97, 114, 101, 97 };
34+
#endif
2335
// AspNetCorePageBytes = MessagePack.Serialize("aspnet_core.page");
36+
#if NETCOREAPP
2437
private static ReadOnlySpan<byte> AspNetCorePageBytes => new byte[] { 176, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 112, 97, 103, 101 };
38+
#else
39+
private static readonly byte[] AspNetCorePageBytes = new byte[] { 176, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 112, 97, 103, 101 };
40+
#endif
2541

2642
public override string? GetTag(string key)
2743
{

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AspNetCoreTags.g.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,23 @@ namespace Datadog.Trace.Tagging
1515
partial class AspNetCoreTags
1616
{
1717
// InstrumentationNameBytes = MessagePack.Serialize("component");
18+
#if NETCOREAPP
1819
private static ReadOnlySpan<byte> InstrumentationNameBytes => new byte[] { 169, 99, 111, 109, 112, 111, 110, 101, 110, 116 };
20+
#else
21+
private static readonly byte[] InstrumentationNameBytes = new byte[] { 169, 99, 111, 109, 112, 111, 110, 101, 110, 116 };
22+
#endif
1923
// AspNetCoreRouteBytes = MessagePack.Serialize("aspnet_core.route");
24+
#if NETCOREAPP
2025
private static ReadOnlySpan<byte> AspNetCoreRouteBytes => new byte[] { 177, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 114, 111, 117, 116, 101 };
26+
#else
27+
private static readonly byte[] AspNetCoreRouteBytes = new byte[] { 177, 97, 115, 112, 110, 101, 116, 95, 99, 111, 114, 101, 46, 114, 111, 117, 116, 101 };
28+
#endif
2129
// HttpRouteBytes = MessagePack.Serialize("http.route");
30+
#if NETCOREAPP
2231
private static ReadOnlySpan<byte> HttpRouteBytes => new byte[] { 170, 104, 116, 116, 112, 46, 114, 111, 117, 116, 101 };
32+
#else
33+
private static readonly byte[] HttpRouteBytes = new byte[] { 170, 104, 116, 116, 112, 46, 114, 111, 117, 116, 101 };
34+
#endif
2335

2436
public override string? GetTag(string key)
2537
{

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AspNetTags.g.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,41 @@ namespace Datadog.Trace.Tagging
1515
partial class AspNetTags
1616
{
1717
// AspNetRouteBytes = MessagePack.Serialize("aspnet.route");
18+
#if NETCOREAPP
1819
private static ReadOnlySpan<byte> AspNetRouteBytes => new byte[] { 172, 97, 115, 112, 110, 101, 116, 46, 114, 111, 117, 116, 101 };
20+
#else
21+
private static readonly byte[] AspNetRouteBytes = new byte[] { 172, 97, 115, 112, 110, 101, 116, 46, 114, 111, 117, 116, 101 };
22+
#endif
1923
// AspNetControllerBytes = MessagePack.Serialize("aspnet.controller");
24+
#if NETCOREAPP
2025
private static ReadOnlySpan<byte> AspNetControllerBytes => new byte[] { 177, 97, 115, 112, 110, 101, 116, 46, 99, 111, 110, 116, 114, 111, 108, 108, 101, 114 };
26+
#else
27+
private static readonly byte[] AspNetControllerBytes = new byte[] { 177, 97, 115, 112, 110, 101, 116, 46, 99, 111, 110, 116, 114, 111, 108, 108, 101, 114 };
28+
#endif
2129
// AspNetActionBytes = MessagePack.Serialize("aspnet.action");
30+
#if NETCOREAPP
2231
private static ReadOnlySpan<byte> AspNetActionBytes => new byte[] { 173, 97, 115, 112, 110, 101, 116, 46, 97, 99, 116, 105, 111, 110 };
32+
#else
33+
private static readonly byte[] AspNetActionBytes = new byte[] { 173, 97, 115, 112, 110, 101, 116, 46, 97, 99, 116, 105, 111, 110 };
34+
#endif
2335
// AspNetAreaBytes = MessagePack.Serialize("aspnet.area");
36+
#if NETCOREAPP
2437
private static ReadOnlySpan<byte> AspNetAreaBytes => new byte[] { 171, 97, 115, 112, 110, 101, 116, 46, 97, 114, 101, 97 };
38+
#else
39+
private static readonly byte[] AspNetAreaBytes = new byte[] { 171, 97, 115, 112, 110, 101, 116, 46, 97, 114, 101, 97 };
40+
#endif
2541
// HttpRouteBytes = MessagePack.Serialize("http.route");
42+
#if NETCOREAPP
2643
private static ReadOnlySpan<byte> HttpRouteBytes => new byte[] { 170, 104, 116, 116, 112, 46, 114, 111, 117, 116, 101 };
44+
#else
45+
private static readonly byte[] HttpRouteBytes = new byte[] { 170, 104, 116, 116, 112, 46, 114, 111, 117, 116, 101 };
46+
#endif
2747
// InstrumentationNameBytes = MessagePack.Serialize("component");
48+
#if NETCOREAPP
2849
private static ReadOnlySpan<byte> InstrumentationNameBytes => new byte[] { 169, 99, 111, 109, 112, 111, 110, 101, 110, 116 };
50+
#else
51+
private static readonly byte[] InstrumentationNameBytes = new byte[] { 169, 99, 111, 109, 112, 111, 110, 101, 110, 116 };
52+
#endif
2953

3054
public override string? GetTag(string key)
3155
{

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AwsDynamoDbTags.g.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@ namespace Datadog.Trace.Tagging
1515
partial class AwsDynamoDbTags
1616
{
1717
// TableNameBytes = MessagePack.Serialize("tablename");
18+
#if NETCOREAPP
1819
private static ReadOnlySpan<byte> TableNameBytes => new byte[] { 169, 116, 97, 98, 108, 101, 110, 97, 109, 101 };
20+
#else
21+
private static readonly byte[] TableNameBytes = new byte[] { 169, 116, 97, 98, 108, 101, 110, 97, 109, 101 };
22+
#endif
1923
// SpanKindBytes = MessagePack.Serialize("span.kind");
24+
#if NETCOREAPP
2025
private static ReadOnlySpan<byte> SpanKindBytes => new byte[] { 169, 115, 112, 97, 110, 46, 107, 105, 110, 100 };
26+
#else
27+
private static readonly byte[] SpanKindBytes = new byte[] { 169, 115, 112, 97, 110, 46, 107, 105, 110, 100 };
28+
#endif
2129

2230
public override string? GetTag(string key)
2331
{

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AwsEventBridgeTags.g.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@ namespace Datadog.Trace.Tagging
1515
partial class AwsEventBridgeTags
1616
{
1717
// RuleNameBytes = MessagePack.Serialize("rulename");
18+
#if NETCOREAPP
1819
private static ReadOnlySpan<byte> RuleNameBytes => new byte[] { 168, 114, 117, 108, 101, 110, 97, 109, 101 };
20+
#else
21+
private static readonly byte[] RuleNameBytes = new byte[] { 168, 114, 117, 108, 101, 110, 97, 109, 101 };
22+
#endif
1923
// SpanKindBytes = MessagePack.Serialize("span.kind");
24+
#if NETCOREAPP
2025
private static ReadOnlySpan<byte> SpanKindBytes => new byte[] { 169, 115, 112, 97, 110, 46, 107, 105, 110, 100 };
26+
#else
27+
private static readonly byte[] SpanKindBytes = new byte[] { 169, 115, 112, 97, 110, 46, 107, 105, 110, 100 };
28+
#endif
2129

2230
public override string? GetTag(string key)
2331
{

tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AwsKinesisTags.g.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@ namespace Datadog.Trace.Tagging
1515
partial class AwsKinesisTags
1616
{
1717
// StreamNameBytes = MessagePack.Serialize("streamname");
18+
#if NETCOREAPP
1819
private static ReadOnlySpan<byte> StreamNameBytes => new byte[] { 170, 115, 116, 114, 101, 97, 109, 110, 97, 109, 101 };
20+
#else
21+
private static readonly byte[] StreamNameBytes = new byte[] { 170, 115, 116, 114, 101, 97, 109, 110, 97, 109, 101 };
22+
#endif
1923
// SpanKindBytes = MessagePack.Serialize("span.kind");
24+
#if NETCOREAPP
2025
private static ReadOnlySpan<byte> SpanKindBytes => new byte[] { 169, 115, 112, 97, 110, 46, 107, 105, 110, 100 };
26+
#else
27+
private static readonly byte[] SpanKindBytes = new byte[] { 169, 115, 112, 97, 110, 46, 107, 105, 110, 100 };
28+
#endif
2129

2230
public override string? GetTag(string key)
2331
{

0 commit comments

Comments
 (0)