Skip to content

[perf] Avoid boxing TagList in Activity.AddException - #134645

Merged
tarekgh merged 1 commit into
dotnet:mainfrom
martincostello:avoid-taglist-box
Sep 25, 2026
Merged

tarekgh merged 1 commit into
dotnet:mainfrom
martincostello:avoid-taglist-box

Conversation

@martincostello

Copy link
Copy Markdown
Member

Avoid boxing TagList plus an enumerator when Activity.AddException() is used by adding a span-based fast path.

Benchmarks

BenchmarkDotNet v0.15.4, Windows 11 (10.0.26200.9457)
13th Gen Intel Core i7-13700H 2.90GHz, 1 CPU, 20 logical and 14 physical cores
.NET SDK 11.0.100-rc.1.26420.103
  [Host] : .NET 11.0.0 (11.0.0-rc.1.26420.103, 11.0.26.42103), X64 RyuJIT x86-64-v3

Job=MediumRun  Toolchain=InProcessEmitToolchain  IterationCount=15  LaunchCount=2  WarmupCount=10
Build Method Mean Error StdDev Gen0 Gen1 Allocated
main AddException 3.848 us 0.1399 us 0.1961 us 0.3204 0.1068 4.01 KB
PR AddException 3.712 us 0.2154 us 0.3019 us 0.2975 0.1068 3.69 KB

vs. main: -3.5% time, -8.0% allocated (-320 B)

Benchmark code
using System.Diagnostics;
using BenchmarkDotNet.Attributes;

namespace System.Diagnostics.Microbenchmarks;

[MemoryDiagnoser]
public class ActivityAddExceptionBenchmarks
{
    private ActivitySource _source = null!;
    private ActivityListener _listener = null!;
    private Activity _activity = null!;
    private Exception _exception = null!;

    [GlobalSetup]
    public void Setup()
    {
        _source = new ActivitySource(nameof(ActivityAddExceptionBenchmarks));

        _listener = new ActivityListener
        {
            ShouldListenTo = _ => true,
            Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,
        };
        ActivitySource.AddActivityListener(_listener);

        _activity = _source.StartActivity("benchmark-activity")!;
        _exception = new InvalidOperationException("Benchmark exception message");

        // Force the stack trace to be materialized once up front, same as it would be by the time
        // a real exception reaches instrumentation code.
        try
        {
            throw _exception;
        }
        catch (InvalidOperationException)
        {
            // Intentionally swallowed: we only want a populated stack trace for the benchmark.
        }
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _activity.Dispose();
        _source.Dispose();
        _listener.Dispose();
    }

    [Benchmark]
    public Activity AddException() => _activity.AddException(_exception);
}

Avoid boxing `TagList` plus an enumerator when `Activity.AddException()` is used by adding a span-based fast path.
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Sep 25, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@martincostello
martincostello requested a balanced review from Copilot September 25, 2026 10:07
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @dotnet/area-system-diagnostics-tracing
See info in area-owners.md if you want to be subscribed.

Copilot AI 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.

Copilot review overview

🟢 Approval recommended

The focused optimization preserves tag ordering and behavior, retains cross-target compatibility, and is supported by allocation benchmarks and existing tests.

Review effort: Balanced
Findings: None

What changed in this PR

Optimizes Activity.AddException by copying TagList entries directly from a span, avoiding two boxing allocations on modern .NET.

Changes:

  • Adds a span-based TagsLinkedList constructor.
  • Uses the fast path while preserving the existing fallback for other targets.
File Description
ActivityEvent.cs Selects the span-based tag-copy path on .NET.
Activity.cs Adds direct linked-list construction from a tag span.

@tarekgh

tarekgh commented Sep 25, 2026

Copy link
Copy Markdown
Member

/ba-g the failure is unrelated

@tarekgh tarekgh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@tarekgh
tarekgh merged commit 9d2d014 into dotnet:main Sep 25, 2026
92 of 94 checks passed
@martincostello
martincostello deleted the avoid-taglist-box branch September 25, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Diagnostics.Tracing community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants