Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[API Proposal]: Add TagList constructor to Measurement #104015

@stevejgordon

Description

@stevejgordon

Background and motivation

Currently, if a consumer creating a Measurement wants to provide tags, several ctor overloads are available. One possibility when working with metrics is that the consumer creates a TagList that holds tags for use with measurements. The TagList struct is designed for performance cases and avoids allocating an array if the number of tags is less than nine. When passing a TagList to the constructor for a new Measurement, I noticed that it resolves to the IEnumerable<KeyValuePair<string, object?>> overload. This issue is because it causes the TagList to be boxed, allocating 160B on the heap.

Since this type is intended to improve performance and reduce allocations, I propose creating a specific ctor overload on Measurement to avoid the boxing. I also propose we use the in parameter modifier to avoid copying where possible. I believe ref readonly would be more correct here, but that would introduce a break for any existing call sites as they would be required to add the ref keyword to pass their argument.

Based on a local POC, I ran some benchmarks with the proposed API and an initial implementation.

| Method          | Size | Mean       | Error     | StdDev    | Median     | Ratio    | RatioSD | Gen0   | Gen1   | Allocated | Alloc Ratio |
|---------------- |----- |-----------:|----------:|----------:|-----------:|---------:|--------:|-------:|-------:|----------:|------------:|
| TagListOriginal | 1    |  36.610 ns | 0.7556 ns | 1.2415 ns |  36.396 ns | baseline |         | 0.0216 |      - |     272 B |             |
| TagListNewIn    | 1    |   6.684 ns | 0.1508 ns | 0.1613 ns |   6.685 ns |     -82% |    3.5% | 0.0032 |      - |      40 B |        -85% |
| TagListNewNoIn  | 1    |  11.576 ns | 0.2476 ns | 0.2316 ns |  11.520 ns |     -69% |    3.7% | 0.0032 |      - |      40 B |        -85% |
|                 |      |            |           |           |            |          |         |        |        |           |             |
| TagListOriginal | 8    |  71.095 ns | 1.4430 ns | 1.8249 ns |  70.799 ns | baseline |         | 0.0395 |      - |     496 B |             |
| TagListNewIn    | 8    |  34.036 ns | 0.6731 ns | 0.9214 ns |  33.787 ns |     -52% |    3.2% | 0.0121 |      - |     152 B |        -69% |
| TagListNewNoIn  | 8    |  39.731 ns | 0.7731 ns | 0.6853 ns |  39.720 ns |     -44% |    3.3% | 0.0121 |      - |     152 B |        -69% |
|                 |      |            |           |           |            |          |         |        |        |           |             |
| TagListOriginal | 100  | 165.453 ns | 3.1845 ns | 3.1276 ns | 165.638 ns | baseline |         | 0.2742 | 0.0017 |    3440 B |             |
| TagListNewIn    | 100  |  71.436 ns | 1.1935 ns | 1.2256 ns |  71.489 ns |     -57% |    2.7% | 0.1293 |      - |    1624 B |        -53% |
| TagListNewNoIn  | 100  |  78.025 ns | 1.8611 ns | 5.3698 ns |  76.523 ns |     -49% |    6.6% | 0.1293 |      - |    1624 B |        -53% |

/cc @tarekgh

API Proposal

namespace System.Diagnostics.Metrics;

public readonly struct Measurement<T> where T : struct
{
    public Measurement(T value) { }
    public Measurement(T value, IEnumerable<KeyValuePair<string, object?>>? tags) { }
    public Measurement(T value, params KeyValuePair<string, object?>[]? tags) { }
    public Measurement(T value, params ReadOnlySpan<KeyValuePair<string, object?>> tags) { }
+   public Measurement(T value, in TagList tags) { }
 
    ...
}

API Usage

var tagList = new TagList(
    new KeyValuePair<string, object?>("key1", "value1"),
    new KeyValuePair<string, object?>("key2", "value2"));

var measurement = new Measurement<long>(10, in tagList);

NOTE: The in keyword is optional, so non-breaking.

Alternative Designs

Add a factory method to Measurement to cover this scenario. A disadvantage is that the existing customer code would continue to cause boxing via the IEnumerable ctor, so there's no "free" performance gain from upgrading to a new version of .NET.

public static Measurement<T> Create(T value, in TagList tags);

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-System.Diagnostics.MetricblockingMarks issues that we want to fast track in order to unblock other important work

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions