YamlDotNet icon indicating copy to clipboard operation
YamlDotNet copied to clipboard

Use Attribute.GetCustomAttributes to reduce allocations / improve performance

Open MattKotsenas opened this issue 1 year ago • 1 comments
trafficstars

Because the project no longer targets netstandard1.3, we can replace the custom implementation inside ReflectionExtensions.GetAllCustomAttributes() with Attribute.GetCustomAttributes().

In addition to simpler code, in the provided benchmark this eliminates 4 List<Attribute> allocations per serialized element. In the benchmark that results in ~10% less CPU time and ~5% fewer allocations.

Here's the added BenchmarkDotNet results on my machine:

Before

// * Summary *

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22635.4010)
Intel Core i9-10940X CPU 3.30GHz, 1 CPU, 28 logical and 14 physical cores
.NET SDK 9.0.100-preview.7.24407.12
  [Host]                       : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX-512F+CD+BW+DQ+VL
  MediumRun-.NET 8.0           : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX-512F+CD+BW+DQ+VL
  MediumRun-.NET Framework 4.7 : .NET Framework 4.8.1 (4.8.9261.0), X64 RyuJIT VectorSize=256

IterationCount=15  LaunchCount=2  WarmupCount=10

| Method     | Job                          | Runtime            | Mean      | Error    | StdDev   | Gen0      | Gen1     | Gen2     | Allocated |
|----------- |----------------------------- |------------------- |----------:|---------:|---------:|----------:|---------:|---------:|----------:|
| Serializer | MediumRun-.NET 8.0           | .NET 8.0           |  48.12 ms | 1.434 ms | 2.057 ms | 2000.0000 | 500.0000 |        - |  24.44 MB |
| Serializer | MediumRun-.NET Framework 4.7 | .NET Framework 4.7 | 108.69 ms | 2.218 ms | 3.181 ms | 7800.0000 | 600.0000 | 200.0000 |     48 MB |

// * Warnings *
MinIterationTime
  SerializationBenchmarks.Serializer: MediumRun-.NET 8.0 -> The minimum observed iteration time is 88.232ms which is very small. It's recommended to increase it to at least 100ms using more operations.

// * Hints *
Outliers
  SerializationBenchmarks.Serializer: MediumRun-.NET 8.0 -> 2 outliers were removed (55.60 ms, 58.61 ms)

After

// * Summary *

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22635.4010)
Intel Core i9-10940X CPU 3.30GHz, 1 CPU, 28 logical and 14 physical cores
.NET SDK 9.0.100-preview.7.24407.12
  [Host]                       : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX-512F+CD+BW+DQ+VL
  MediumRun-.NET 8.0           : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX-512F+CD+BW+DQ+VL
  MediumRun-.NET Framework 4.7 : .NET Framework 4.8.1 (4.8.9261.0), X64 RyuJIT VectorSize=256

IterationCount=15  LaunchCount=2  WarmupCount=10

| Method     | Job                          | Runtime            | Mean     | Error    | StdDev   | Gen0      | Gen1     | Gen2     | Allocated |
|----------- |----------------------------- |------------------- |---------:|---------:|---------:|----------:|---------:|---------:|----------:|
| Serializer | MediumRun-.NET 8.0           | .NET 8.0           | 44.42 ms | 1.825 ms | 2.731 ms | 2000.0000 | 500.0000 |        - |  23.21 MB |
| Serializer | MediumRun-.NET Framework 4.7 | .NET Framework 4.7 | 94.75 ms | 1.211 ms | 1.774 ms | 7333.3333 | 666.6667 | 166.6667 |  45.55 MB |

// * Warnings *
MultimodalDistribution
  SerializationBenchmarks.Serializer: MediumRun-.NET 8.0 -> It seems that the distribution can have several modes (mValue = 3.17)
MinIterationTime
  SerializationBenchmarks.Serializer: MediumRun-.NET 8.0 -> The minimum observed iteration time is 78.527ms which is very small. It's recommended to increase it to at least 100ms using more operations.

and here's the object allocation graph for a single iteration of the benchmark:

Before

Type Allocations
| + System.Collections.Generic.List<> 40,103
|| - System.Collections.Generic.List<System.Attribute> 40,004
Function Name Allocations Bytes Module Name
 + YamlDotNet.ReflectionExtensions.GetAllCustomAttributes<>(System.Reflection.PropertyInfo) 40,004 1,280,128 yamldotnet
| + YamlDotNet.Serialization.TypeInspectors.ReadablePropertiesTypeInspector+ReflectionPropertyDescriptor.GetCustomAttribute<T>() 40,004 1,280,128 yamldotnet

After

Type Allocations
| + System.Collections.Generic.List<> 99
|| - System.Collections.Generic.List<System.Text.RegularExpressions.RegexNode> 33

MattKotsenas avatar Aug 15 '24 19:08 MattKotsenas