Skip to content

[perf] Optimise {ActivityTraceId|ActivitySpanId}.CopyTo() - #134135

Merged
tarekgh merged 4 commits into
dotnet:mainfrom
martincostello:improve-activityspantraceid-copyto-perf
Sep 17, 2026
Merged

tarekgh merged 4 commits into
dotnet:mainfrom
martincostello:improve-activityspantraceid-copyto-perf

Conversation

@martincostello

Copy link
Copy Markdown
Member

Optimise ActivityTraceId.CopyTo() and ActivitySpanId.CopyTo() by using Convert.FromHexString() in ActivityTraceId.SetSpanFromHexChars() to benefit from SIMD acceleration, where available, for a ~9x throughput improvement when the IDs realistically vary per invocation.

Adapted from open-telemetry/opentelemetry-dotnet#7775.

Benchmarks

Method Shared ID? Before After Speedup
ActivityTraceId.CopyTo No 117.24 ns 12.31 ns 9.5x
ActivitySpanId.CopyTo No 58.37 ns 6.73 ns 8.7x
ActivityTraceId.CopyTo Yes 21.80 ns 11.82 ns 1.8x
ActivitySpanId.CopyTo Yes 11.80 ns 6.53 ns 1.8x
BenchmarkDotNet v0.15.4, Windows 11
13th Gen Intel Core i7-13700H, 1 CPU, 20 logical and 14 physical cores
.NET SDK 11.0.100-rc.1.26425.128
  [Host] : .NET 10.0.12, X64 RyuJIT x86-64-v3
Code
using System.Diagnostics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(ActivityIdCopyToBenchmarks).Assembly).Run(args);

[MemoryDiagnoser]
public class ActivityIdCopyToBenchmarks
{
    // Large enough that the branch predictor cannot memorize the full sequence of ids when Shared = false.
    private const int PoolSize = 4096;

    private ActivityTraceId[] _traceIdPool = null!;
    private ActivitySpanId[] _spanIdPool = null!;
    private readonly byte[] _traceIdDestination = new byte[16];
    private readonly byte[] _spanIdDestination = new byte[8];
    private int _index;

    [Params(false, true)]
    public bool Shared { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        _traceIdPool = new ActivityTraceId[PoolSize];
        _spanIdPool = new ActivitySpanId[PoolSize];

        if (Shared)
        {
            ActivityTraceId sharedTraceId = ActivityTraceId.CreateRandom();
            ActivitySpanId sharedSpanId = ActivitySpanId.CreateRandom();

            Array.Fill(_traceIdPool, sharedTraceId);
            Array.Fill(_spanIdPool, sharedSpanId);
        }
        else
        {
            for (int i = 0; i < PoolSize; i++)
            {
                _traceIdPool[i] = ActivityTraceId.CreateRandom();
                _spanIdPool[i] = ActivitySpanId.CreateRandom();
            }
        }

        _index = 0;
    }

    [Benchmark]
    public void TraceIdCopyTo()
    {
        _traceIdPool[_index++ & (PoolSize - 1)].CopyTo(_traceIdDestination);
    }

    [Benchmark]
    public void SpanIdCopyTo()
    {
        _spanIdPool[_index++ & (PoolSize - 1)].CopyTo(_spanIdDestination);
    }
}

Optimise `ActivityTraceId.CopyTo()` and `ActivitySpanId.CopyTo()` by using `Convert.FromHexString()` in `ActivityTraceId.SetSpanFromHexChars()` to benefit from SIMD acceleration, where available, for a ~9x throughput improvement.
Copilot AI lite review requested due to automatic review settings September 17, 2026 13:09
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Sep 17, 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.

@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.

🟡 Changes recommended

The new exception path can surface an argument name (“outBytes”) that doesn’t match the public CopyTo(... destination ...) parameter, making the resulting error message confusing.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Optimizes ActivityTraceId.CopyTo() / ActivitySpanId.CopyTo() by switching the internal hex-to-bytes conversion helper to use Convert.FromHexString(...) (on #if NET builds), aiming to leverage SIMD-accelerated hex decoding where available.

Changes:

  • Updated ActivityTraceId.SetSpanFromHexChars(...) to decode hex via Convert.FromHexString on NET builds.
  • Added a stricter success check (OperationStatus.Done and expected bytesWritten) with an exception on mismatch.
  • Clarified the helper’s length precondition in the XML comment.
File summaries
File Description
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs Uses span-based Convert.FromHexString in SetSpanFromHexChars to speed up CopyTo() decoding on NET builds.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

Rename parameter to match the public callers' parameter name.
Copilot AI review requested due to automatic review settings September 17, 2026 13:20

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.

🟡 Changes recommended

The #else path still relies on Debug.Assert for a public-facing invariant, which can result in partial/truncated writes in Release builds and inconsistent behavior vs the new #if NET throwing behavior.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 17, 2026 16:44

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.

🟡 Changes recommended

The #if NET path can partially write to destination before throwing on length mismatch, which is inconsistent with the non-NET path and should be guarded up-front (and ideally covered by tests).

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 2
  • Review effort level: Lite

- Validate destination length up-front.
- Add test coverage for `ArgumentOutOfRangeException` being thrown when the destination is the wrong size.
Copilot AI review requested due to automatic review settings September 17, 2026 17:10

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.

🟢 Approval recommended

The change is localized, preserves correct behavior for valid inputs, improves performance via the NET fast path, and adds targeted regression tests for the updated error handling.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@MihaZupan MihaZupan added this to the 12.0.0 milestone Sep 17, 2026
@tarekgh
tarekgh merged commit ccace4c into dotnet:main Sep 17, 2026
78 of 81 checks passed
@martincostello
martincostello deleted the improve-activityspantraceid-copyto-perf branch September 17, 2026 21:24
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.

4 participants