Skip to content

Conversation

@iliar-turdushev
Copy link

@iliar-turdushev iliar-turdushev commented Nov 25, 2025

Fixes #5976

Changes

The PR moves BucketLookup tree to AggregateStore. A logic that builds BucketLookup tree and decides whether a linear or binary search should be used has been moved to a class called HistogramExplicitBounds.

Benchmark results

HistogramBenchmarks measure performance of histograms with explicit bucket boundaries. Below are sample benchmark results comparing performance "before" and "after".

BEFORE:

BenchmarkDotNet v0.15.6, Windows 11 (10.0.26200.7171)
AMD Ryzen AI 7 PRO 350 w/ Radeon 860M 2.00GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 10.0.100
  [Host]     : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4
  DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4

| Method                      | BoundCount | Mean      | Error     | StdDev     | Median    |
|---------------------------- |----------- |----------:|----------:|-----------:|----------:|
| HistogramHotPath            | 10         |  43.28 ns |  0.727 ns |   0.568 ns |  43.34 ns |
| HistogramWith1LabelHotPath  | 10         |  97.21 ns |  4.526 ns |  13.344 ns |  98.04 ns |
| HistogramWith3LabelsHotPath | 10         | 173.01 ns |  5.966 ns |  16.331 ns | 167.71 ns |
| HistogramWith5LabelsHotPath | 10         | 234.35 ns | 11.753 ns |  33.150 ns | 223.43 ns |
| HistogramWith7LabelsHotPath | 10         | 255.74 ns |  4.940 ns |   8.389 ns | 255.55 ns |
| HistogramHotPath            | 49         |  44.88 ns |  0.916 ns |   1.370 ns |  44.46 ns |
| HistogramWith1LabelHotPath  | 49         |  93.82 ns |  1.496 ns |   1.400 ns |  93.93 ns |
| HistogramWith3LabelsHotPath | 49         | 200.73 ns |  4.227 ns |  11.642 ns | 197.86 ns |
| HistogramWith5LabelsHotPath | 49         | 253.97 ns |  8.755 ns |  24.838 ns | 249.11 ns |
| HistogramWith7LabelsHotPath | 49         | 285.84 ns |  5.685 ns |  13.400 ns | 286.48 ns |
| HistogramHotPath            | 50         |  55.65 ns |  1.130 ns |   2.231 ns |  54.87 ns |
| HistogramWith1LabelHotPath  | 50         | 120.27 ns |  2.961 ns |   7.903 ns | 119.97 ns |
| HistogramWith3LabelsHotPath | 50         | 270.13 ns | 11.557 ns |  32.407 ns | 255.96 ns |
| HistogramWith5LabelsHotPath | 50         | 373.36 ns | 18.914 ns |  53.962 ns | 356.76 ns |
| HistogramWith7LabelsHotPath | 50         | 417.53 ns | 25.891 ns |  73.868 ns | 396.03 ns |
| HistogramHotPath            | 1000       |  86.23 ns |  2.101 ns |   5.821 ns |  84.36 ns |
| HistogramWith1LabelHotPath  | 1000       | 141.14 ns |  2.794 ns |   6.075 ns | 140.33 ns |
| HistogramWith3LabelsHotPath | 1000       | 702.68 ns | 26.049 ns |  75.987 ns | 681.34 ns |
| HistogramWith5LabelsHotPath | 1000       | 665.76 ns | 13.336 ns |  24.047 ns | 663.69 ns |
| HistogramWith7LabelsHotPath | 1000       | 874.01 ns | 52.336 ns | 154.314 ns | 829.17 ns |

AFTER:

BenchmarkDotNet v0.15.6, Windows 11 (10.0.26200.7171)
AMD Ryzen AI 7 PRO 350 w/ Radeon 860M 2.00GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 10.0.100
  [Host]     : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4
  DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4

| Method                      | BoundCount | Mean      | Error    | StdDev    | Median    |
|---------------------------- |----------- |----------:|---------:|----------:|----------:|
| HistogramHotPath            | 10         |  42.64 ns | 1.088 ns |  2.808 ns |  42.94 ns |
| HistogramWith1LabelHotPath  | 10         |  92.51 ns | 1.817 ns |  2.362 ns |  92.14 ns |
| HistogramWith3LabelsHotPath | 10         | 163.94 ns | 3.304 ns |  5.144 ns | 163.25 ns |
| HistogramWith5LabelsHotPath | 10         | 222.49 ns | 4.473 ns |  5.817 ns | 221.39 ns |
| HistogramWith7LabelsHotPath | 10         | 268.24 ns | 4.758 ns |  5.843 ns | 269.82 ns |
| HistogramHotPath            | 49         |  47.98 ns | 0.958 ns |  0.941 ns |  48.11 ns |
| HistogramWith1LabelHotPath  | 49         | 110.55 ns | 1.723 ns |  1.527 ns | 110.87 ns |
| HistogramWith3LabelsHotPath | 49         | 179.00 ns | 3.389 ns |  4.968 ns | 179.27 ns |
| HistogramWith5LabelsHotPath | 49         | 237.62 ns | 4.768 ns |  7.423 ns | 237.49 ns |
| HistogramWith7LabelsHotPath | 49         | 291.57 ns | 5.750 ns | 10.658 ns | 293.77 ns |
| HistogramHotPath            | 50         |  56.80 ns | 1.126 ns |  1.382 ns |  56.24 ns |
| HistogramWith1LabelHotPath  | 50         | 108.26 ns | 2.157 ns |  1.801 ns | 107.97 ns |
| HistogramWith3LabelsHotPath | 50         | 207.34 ns | 3.589 ns |  4.407 ns | 207.46 ns |
| HistogramWith5LabelsHotPath | 50         | 253.32 ns | 4.531 ns |  4.239 ns | 253.17 ns |
| HistogramWith7LabelsHotPath | 50         | 317.08 ns | 9.062 ns | 26.577 ns | 307.90 ns |
| HistogramHotPath            | 1000       |  85.44 ns | 1.714 ns |  1.974 ns |  85.29 ns |
| HistogramWith1LabelHotPath  | 1000       | 139.51 ns | 2.507 ns |  2.222 ns | 139.65 ns |
| HistogramWith3LabelsHotPath | 1000       | 306.76 ns | 5.566 ns |  9.748 ns | 305.89 ns |
| HistogramWith5LabelsHotPath | 1000       | 373.47 ns | 7.277 ns | 11.329 ns | 372.39 ns |
| HistogramWith7LabelsHotPath | 1000       | 424.75 ns | 8.376 ns | 15.104 ns | 423.88 ns |

The benchmark tests were run several times and the results were the same as above:

  • for a number of explicit bucket bounds (BoundsCount) less than 50 the performance "before" and "after" is more or less the same;
  • for BoundsCount >= 50 the performance "after" is better than "before", and the higher the cardinality of a metric the better performance.

Memory consumption

A test described in this comment was used to compare memory consumption "before" and "after":

  • memory consumption "before" is ~750-800 MB;
  • "after" - 170-180 MB.

Merge requirement checklist

  • CONTRIBUTING guidelines followed (license requirements, nullable enabled, static analysis, etc.)
  • Unit tests added/updated
  • Appropriate CHANGELOG.md files updated for non-trivial changes
  • Changes in public API reviewed (if applicable) - NOT APPLICABLE

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Nov 25, 2025

CLA Signed

The committers listed above are authorized under a signed CLA.

@github-actions github-actions bot added the pkg:OpenTelemetry Issues related to OpenTelemetry NuGet package label Nov 25, 2025
@iliar-turdushev iliar-turdushev changed the title [DRAFT, DO NOT REVIEW] Move BucketLookup to AggregateStore [DRAFT, DO NOT REVIEW] Move BucketLookup tree to AggregateStore Nov 25, 2025
@iliar-turdushev iliar-turdushev changed the title [DRAFT, DO NOT REVIEW] Move BucketLookup tree to AggregateStore [DRAFT, DO NOT REVIEW] Move BucketLookup tree to AggregatorStore Nov 26, 2025
@iliar-turdushev iliar-turdushev changed the title [DRAFT, DO NOT REVIEW] Move BucketLookup tree to AggregatorStore Move BucketLookup tree to AggregatorStore Nov 26, 2025
@iliar-turdushev iliar-turdushev marked this pull request as ready for review November 26, 2025 15:29
@iliar-turdushev iliar-turdushev requested a review from a team as a code owner November 26, 2025 15:29
@martincostello martincostello changed the title Move BucketLookup tree to AggregatorStore [OpenTelemetry] Move BucketLookup tree to AggregatorStore Nov 26, 2025
@codecov
Copy link

codecov bot commented Nov 26, 2025

Codecov Report

❌ Patch coverage is 98.36066% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 87.07%. Comparing base (ef4bb19) to head (697d90b).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/OpenTelemetry/Metrics/AggregatorStore.cs 91.66% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #6715      +/-   ##
==========================================
+ Coverage   86.84%   87.07%   +0.22%     
==========================================
  Files         258      259       +1     
  Lines       11984    12198     +214     
==========================================
+ Hits        10408    10621     +213     
- Misses       1576     1577       +1     
Flag Coverage Δ
unittests-Project-Experimental 86.68% <98.36%> (-0.10%) ⬇️
unittests-Project-Stable 86.68% <98.36%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...c/OpenTelemetry/Metrics/HistogramExplicitBounds.cs 100.00% <100.00%> (ø)
...nTelemetry/Metrics/MetricPoint/HistogramBuckets.cs 100.00% <100.00%> (ø)
...c/OpenTelemetry/Metrics/MetricPoint/MetricPoint.cs 94.17% <100.00%> (ø)
src/OpenTelemetry/Metrics/AggregatorStore.cs 87.50% <91.66%> (-0.27%) ⬇️

... and 2 files with indirect coverage changes

Co-authored-by: Martin Costello <martin@martincostello.com>
@iliar-turdushev
Copy link
Author

iliar-turdushev commented Nov 27, 2025

@martincostello @Kielek @cijothomas Codecov says that one changed line is not covered with tests, this is the line (you need to expand the AggregatorStore file and scroll to the line 779). The line wasn't covered with test even before my changes. How should we proceed? Can we merge the PR as-is? Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg:OpenTelemetry Issues related to OpenTelemetry NuGet package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Metrics][Perf] Move BucketLookup tree to AggregatorStore to reduce memory usage

4 participants