From 149a17e6589dfabc04677b090a067e2be0d7ce03 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Tue, 7 Jul 2026 14:26:31 +0200
Subject: [PATCH 1/6] perf: add internal LazyLite type
---
src/Sentry/Internal/LazyLite.cs | 38 ++++++++
test/Sentry.Tests/Internals/LazyLiteTests.cs | 92 ++++++++++++++++++++
2 files changed, 130 insertions(+)
create mode 100644 src/Sentry/Internal/LazyLite.cs
create mode 100644 test/Sentry.Tests/Internals/LazyLiteTests.cs
diff --git a/src/Sentry/Internal/LazyLite.cs b/src/Sentry/Internal/LazyLite.cs
new file mode 100644
index 0000000000..3d527aa120
--- /dev/null
+++ b/src/Sentry/Internal/LazyLite.cs
@@ -0,0 +1,38 @@
+namespace Sentry.Internal;
+
+///
+/// A minimal replacement for .
+///
+/// We're using this for local variables, where no is required,
+/// but a value is still cached, without any allocations via the -based .
+///
+///
+/// This type is not thread safe.
+///
+internal struct LazyLite
+{
+ private Func? _factory;
+ private T? _value;
+
+ public LazyLite(Func? valueFactory)
+ {
+ _factory = valueFactory;
+ _value = default;
+ }
+
+ public readonly bool IsValueCreated => _factory is null;
+
+ public T Value => _factory is null ? _value! : CreateValue();
+
+ private T CreateValue()
+ {
+ var factory = _factory;
+ if (factory is not null)
+ {
+ _factory = null;
+ _value = factory();
+ }
+
+ return _value!;
+ }
+}
diff --git a/test/Sentry.Tests/Internals/LazyLiteTests.cs b/test/Sentry.Tests/Internals/LazyLiteTests.cs
new file mode 100644
index 0000000000..cd4cf3aa4e
--- /dev/null
+++ b/test/Sentry.Tests/Internals/LazyLiteTests.cs
@@ -0,0 +1,92 @@
+#nullable enable
+
+namespace Sentry.Tests.Internals;
+
+public class LazyLiteTests
+{
+ private int _valueFactoryInvoked = 0;
+
+ [Fact]
+ public void Default_HasNoFactory_ValueConsideredCreated()
+ {
+ LazyLite lazy = default;
+
+ lazy.IsValueCreated.Should().BeTrue();
+ _valueFactoryInvoked.Should().Be(0);
+
+ lazy.Value.Should().BeNull();
+
+ lazy.IsValueCreated.Should().BeTrue();
+ _valueFactoryInvoked.Should().Be(0);
+ }
+
+ [Fact]
+ public void Null_HasNoFactory_ValueConsideredCreated()
+ {
+ LazyLite lazy = new(null);
+
+ lazy.IsValueCreated.Should().BeTrue();
+ _valueFactoryInvoked.Should().Be(0);
+
+ lazy.Value.Should().BeNull();
+
+ lazy.IsValueCreated.Should().BeTrue();
+ _valueFactoryInvoked.Should().Be(0);
+ }
+
+ [Fact]
+ public void NullFactory_HasFactory_CreatesNullValue()
+ {
+ LazyLite lazy = new(NullFactory);
+
+ lazy.IsValueCreated.Should().BeFalse();
+ _valueFactoryInvoked.Should().Be(0);
+
+ lazy.Value.Should().BeNull();
+
+ lazy.IsValueCreated.Should().BeTrue();
+ _valueFactoryInvoked.Should().Be(1);
+ }
+
+ [Fact]
+ public void ValueFactory_HasFactory_CreatesValue()
+ {
+ LazyLite lazy = new(ValueFactory);
+
+ lazy.IsValueCreated.Should().BeFalse();
+ _valueFactoryInvoked.Should().Be(0);
+
+ lazy.Value.Should().Be("Created");
+
+ lazy.IsValueCreated.Should().BeTrue();
+ _valueFactoryInvoked.Should().Be(1);
+ }
+
+ [Fact]
+ public void ValueFactory_HasFactory_ReuseCachedValue()
+ {
+ LazyLite lazy = new(ValueFactory);
+
+ lazy.Value.Should().Be("Created");
+
+ lazy.IsValueCreated.Should().BeTrue();
+ _valueFactoryInvoked.Should().Be(1);
+
+ lazy.Value.Should().Be("Created");
+
+ lazy.IsValueCreated.Should().BeTrue();
+ _valueFactoryInvoked.Should().Be(1);
+ }
+
+ private string? NullFactory()
+ {
+ _valueFactoryInvoked++;
+ return null;
+ }
+
+ private string ValueFactory()
+ {
+ _valueFactoryInvoked++;
+ return "Created";
+ }
+}
From 43a56bad00396b74debc3d3722dd1490f8d3904e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Tue, 7 Jul 2026 18:43:58 +0200
Subject: [PATCH 2/6] ref: move BatchProcessorBenchmarks to Internal namespace
---
...nchmarks.Internal.BatchProcessorBenchmarks-report-github.md} | 0
.../{ => Internal}/BatchProcessorBenchmarks.cs | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
rename benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/{Sentry.Benchmarks.BatchProcessorBenchmarks-report-github.md => Sentry.Benchmarks.Internal.BatchProcessorBenchmarks-report-github.md} (100%)
rename benchmarks/Sentry.Benchmarks/{ => Internal}/BatchProcessorBenchmarks.cs (98%)
diff --git a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.BatchProcessorBenchmarks-report-github.md b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.Internal.BatchProcessorBenchmarks-report-github.md
similarity index 100%
rename from benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.BatchProcessorBenchmarks-report-github.md
rename to benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.Internal.BatchProcessorBenchmarks-report-github.md
diff --git a/benchmarks/Sentry.Benchmarks/BatchProcessorBenchmarks.cs b/benchmarks/Sentry.Benchmarks/Internal/BatchProcessorBenchmarks.cs
similarity index 98%
rename from benchmarks/Sentry.Benchmarks/BatchProcessorBenchmarks.cs
rename to benchmarks/Sentry.Benchmarks/Internal/BatchProcessorBenchmarks.cs
index 51e0394ee1..77be6c1671 100644
--- a/benchmarks/Sentry.Benchmarks/BatchProcessorBenchmarks.cs
+++ b/benchmarks/Sentry.Benchmarks/Internal/BatchProcessorBenchmarks.cs
@@ -2,7 +2,7 @@
using Sentry.Extensibility;
using Sentry.Internal;
-namespace Sentry.Benchmarks;
+namespace Sentry.Benchmarks.Internal;
///
/// (formerly "Sentry.Internal.StructuredLogBatchProcessor") was originally developed as Batch Processor for Logs only.
From 0533236f96eca7b8f7627f4ceae7ce8508793c00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Tue, 7 Jul 2026 19:16:07 +0200
Subject: [PATCH 3/6] perf: add Benchmarks
---
...s.Internal.LazyBenchmarks-report-github.md | 14 ++++++
.../Internal/LazyBenchmarks.cs | 44 +++++++++++++++++++
2 files changed, 58 insertions(+)
create mode 100644 benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.Internal.LazyBenchmarks-report-github.md
create mode 100644 benchmarks/Sentry.Benchmarks/Internal/LazyBenchmarks.cs
diff --git a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.Internal.LazyBenchmarks-report-github.md b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.Internal.LazyBenchmarks-report-github.md
new file mode 100644
index 0000000000..e5636e5ea0
--- /dev/null
+++ b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.Internal.LazyBenchmarks-report-github.md
@@ -0,0 +1,14 @@
+```
+
+BenchmarkDotNet v0.15.8, macOS Tahoe 26.5.1 (25F80) [Darwin 25.5.0]
+Apple M3 Pro, 1 CPU, 12 logical and 12 physical cores
+.NET SDK 10.0.301
+ [Host] : .NET 10.0.9 (10.0.9, 10.0.926.27113), Arm64 RyuJIT armv8.0-a
+ DefaultJob : .NET 10.0.9 (10.0.9, 10.0.926.27113), Arm64 RyuJIT armv8.0-a
+
+
+```
+| Method | Mean | Error | StdDev | Gen0 | Allocated |
+|------------------------- |---------:|----------:|----------:|-------:|----------:|
+| System_Lazy | 6.909 ns | 0.0807 ns | 0.0715 ns | 0.0048 | 40 B |
+| Sentry_Internal_LazyLite | 1.006 ns | 0.0102 ns | 0.0085 ns | - | - |
diff --git a/benchmarks/Sentry.Benchmarks/Internal/LazyBenchmarks.cs b/benchmarks/Sentry.Benchmarks/Internal/LazyBenchmarks.cs
new file mode 100644
index 0000000000..760ce3771d
--- /dev/null
+++ b/benchmarks/Sentry.Benchmarks/Internal/LazyBenchmarks.cs
@@ -0,0 +1,44 @@
+#nullable enable
+
+using BenchmarkDotNet.Attributes;
+using Sentry.Internal;
+
+namespace Sentry.Benchmarks.Internal;
+
+public class LazyBenchmarks
+{
+ private static string Text = null!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ Text = "Factory";
+ }
+
+ [Benchmark]
+ public (char, char) System_Lazy()
+ {
+ var lazy = new Lazy(ValueFactory, LazyThreadSafetyMode.None);
+
+ var first = lazy.Value;
+ var second = lazy.Value;
+
+ return (first, second);
+ }
+
+ [Benchmark]
+ public (char, char) Sentry_Internal_LazyLite()
+ {
+ var lazy = new LazyLite(ValueFactory);
+
+ var first = lazy.Value;
+ var second = lazy.Value;
+
+ return (first, second);
+ }
+
+ private static char ValueFactory()
+ {
+ return Text[^1];
+ }
+}
From 84c78516d1585ede167d7f1ae658a59aba6f3f70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Tue, 7 Jul 2026 19:17:09 +0200
Subject: [PATCH 4/6] docs: fix benchmark instructions
---
benchmarks/Sentry.Benchmarks/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/benchmarks/Sentry.Benchmarks/README.md b/benchmarks/Sentry.Benchmarks/README.md
index 73da241446..b50dd7598f 100644
--- a/benchmarks/Sentry.Benchmarks/README.md
+++ b/benchmarks/Sentry.Benchmarks/README.md
@@ -3,5 +3,5 @@
To run benchmarks in a single class:
```shell-script
-dotnet run -c Release -- --filter *StackFrameBenchmarks*
+dotnet run -c Release -- --filter "*StackFrameBenchmarks*"
```
From c08d8b6188f7317eb5599eb936bb45f82585d6c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Tue, 7 Jul 2026 19:19:38 +0200
Subject: [PATCH 5/6] perf: use new local Lazy struct
---
src/Sentry.AspNetCore/SentryTracingMiddleware.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Sentry.AspNetCore/SentryTracingMiddleware.cs b/src/Sentry.AspNetCore/SentryTracingMiddleware.cs
index 63cbd5f0d1..82dd1fc247 100644
--- a/src/Sentry.AspNetCore/SentryTracingMiddleware.cs
+++ b/src/Sentry.AspNetCore/SentryTracingMiddleware.cs
@@ -179,7 +179,7 @@ public async Task InvokeAsync(HttpContext context)
// If no Name was found for Transaction, then we don't have the route.
// Also, run this block if the caller has opted into always calling the TransactionNameProvider.
- var customName = new Lazy(context.TryGetCustomTransactionName, LazyThreadSafetyMode.None);
+ var customName = new LazyLite(context.TryGetCustomTransactionName);
var forceCustomName = _options.PreferTransactionNameProvider && customName.Value is not null;
if (transaction.Name == string.Empty || forceCustomName)
{
From 87f9f467c829c1ba9c4b41bdaaf73f6525f764dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Tue, 7 Jul 2026 19:20:12 +0200
Subject: [PATCH 6/6] docs: update XML documentation comments
---
src/Sentry/Internal/LazyLite.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Sentry/Internal/LazyLite.cs b/src/Sentry/Internal/LazyLite.cs
index 3d527aa120..ae46a0ecf0 100644
--- a/src/Sentry/Internal/LazyLite.cs
+++ b/src/Sentry/Internal/LazyLite.cs
@@ -1,7 +1,7 @@
namespace Sentry.Internal;
///
-/// A minimal replacement for .
+/// A -based variant of that is not thread safe.
///
/// We're using this for local variables, where no is required,
/// but a value is still cached, without any allocations via the -based .