Skip to content

perf: avoid MeasurementUnit enum boxing allocations - #5218

Merged
jamescrosswell merged 7 commits into
mainfrom
fix/issue-4844-measurementunit-alloc
Jul 27, 2026
Merged

perf: avoid MeasurementUnit enum boxing allocations#5218
jamescrosswell merged 7 commits into
mainfrom
fix/issue-4844-measurementunit-alloc

Conversation

@jamescrosswell

@jamescrosswell jamescrosswell commented May 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #4844 by removing avoidable allocations in MeasurementUnit:

  • Replace boxed Enum storage with compact internal representation (UnitKind + int value)
  • Keep custom units as string-backed values
  • Replace Enum.ToString().ToLowerInvariant() with precomputed lowercase name lookups for built-in units
  • Preserve existing behavior for parsing, equality, hashing, and string serialization

This removes both enum boxing and per-call lowercase string allocation on the enum-backed path.

Testing

  • Ran: dotnet test SentryNoMobile.slnf

    • Result: all relevant suites passed for this change
    • Known baseline failures: test/Sentry.AspNet.Tests (net48) has 2 failing tests due to MissingMethodException in SentryAspNetOptionsExtensionsTests, reproducible on main with no local changes.
  • Reproduced baseline on main:

    • dotnet test test/Sentry.AspNet.Tests/Sentry.AspNet.Tests.csproj -f net48
    • Same 2 failures occur on main.

#skip-changelog

Avoid enum boxing and ToLowerInvariant allocations in MeasurementUnit by storing enum kind/value and using precomputed lowercase unit names.

Verified: dotnet test SentryNoMobile.slnf (fails only on known baseline net48 Sentry.AspNet.Tests issues reproducible on main).

#skip-changelog

Co-Authored-By: goose <goose@aaif.dev>
@jamescrosswell
jamescrosswell requested a review from Flash0ver as a code owner May 11, 2026 10:10
@jamescrosswell
jamescrosswell marked this pull request as draft May 11, 2026 10:25

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought:

This is a similar implementation a coding agent on my end suggested when I gave it a try when looking at it with a friend of mine just last weekend 😉.

I'm not too certain if I'm happy with this approach, though:

  • the size of the struct is increasing
  • we embed more data into the assembly (via the string-arrays)

On the other hand, this (or a similar) implementation is necessary, in order to not change the IEquatable-semantics.

We were then experimenting with the struct only having a string field,
but that would change the Equality-semantics, where

  • MeasurementUnit.Duration.Millisecond == MeasurementUnit.Custom("millisecond")
    • old/this: would not be equal, because constructed differently
    • only with string field: would now become equal, being a behavioral change

@Flash0ver Flash0ver May 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait ... actually ... I believe I am wrong concerning the size ... at least partially wrong

  • previous
    • System.Enum + System.String
    • on 64-bit: 8-byte-reference + 8-byte reference = 16B
    • on 32-bit: 4-byte-reference + 4-byte reference = 8B
  • this change
    • UnitKind enum (byte) + System.Int32 + System.String
    • on 64-bit: 1-byte enum + 4-byte int + 3-byte padding, 8-byte reference = 16B
    • on 32-bit: 1-byte enum + 3-byte padding + 4-byte int + 4-byte reference + padding = 12B

So we are only increasing the size of the struct by 4B on 32-bit systems/processes.

Yeah ... that's neglectable considering we no longer box to System.Enum.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

about the Equals-Semantics

If we would only keep a string field, assigning the string via a switch on constructions, we would simplify the code, reduce the struct size to 4B/8B (32-/64-bit systems); but we would change the Equals, as it would no longer matter whether the MeasurementUnit was constructed via MeasurementUnit.Custom or the respective implicit-ennum-conversion.

This being a behavioral change, we could simplify MeasurementUnit in the next Major ... or should we keep the Equals-Behavior as is, where there is a difference how the MeasurementUnit was constructed.

What do you think?

@jamescrosswell jamescrosswell May 11, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To err on the side of caution (since this API is probably quite widely used already), we could delay the behavioural change (and saving the extra 4B) until the next major release. The only reason to release this sooner is that it would avoid potential merge conflicts and double handling (so efficiency).

@bitsandfoxes what do you think? Delay or just go ahead and release?

fwiw I think the minor change in behaviour is very unlikely to bite us... but that's just my take.

@codecov

codecov Bot commented May 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.21%. Comparing base (80f7ff5) to head (7cae1ec).
⚠️ Report is 126 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5218      +/-   ##
==========================================
+ Coverage   74.13%   74.21%   +0.08%     
==========================================
  Files         506      507       +1     
  Lines       18292    18350      +58     
  Branches     3576     3583       +7     
==========================================
+ Hits        13561    13619      +58     
- Misses       3859     3860       +1     
+ Partials      872      871       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/Sentry/MeasurementUnit.cs
Comment thread src/Sentry/MeasurementUnit.cs Outdated
getsentry-bot and others added 5 commits May 11, 2026 22:26
Co-Authored-By: goose <goose-noreply@example.com>
Co-Authored-By: goose <goose-noreply@example.com>
Co-Authored-By: goose <goose-noreply@example.com>
Co-Authored-By: Goose <goose-noreply@example.com>
@jamescrosswell
jamescrosswell marked this pull request as ready for review May 12, 2026 01:49
@jamescrosswell
jamescrosswell marked this pull request as draft July 19, 2026 23:58
@jamescrosswell jamescrosswell added Next Major Changes scheduled for the next Major release. and removed Next Major Changes scheduled for the next Major release. labels Jul 22, 2026
@jamescrosswell
jamescrosswell marked this pull request as ready for review July 22, 2026 06:55
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Jul 22, 2026

@bitsandfoxes bitsandfoxes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@jamescrosswell
jamescrosswell merged commit 2f0203f into main Jul 27, 2026
50 of 51 checks passed
@jamescrosswell
jamescrosswell deleted the fix/issue-4844-measurementunit-alloc branch July 27, 2026 22:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: heap-allocation-free MeasurementUnit

4 participants