Draft
Conversation
84290eb to
197262a
Compare
Replace ConcurrentDictionary with lock-guarded flat arrays for EventContext field storage, preserving thread safety while eliminating hash table overhead and sort-on-render. Stopwatch replaced with Stopwatch.GetTimestamp() to avoid per-event object allocation. Lazy initialization for timers, counts, and PrivateData skips allocation when unused. Constructor batches field writes without locking since the object isn't yet shared. ThreadStatic StringBuilder reuse and single-pass key normalization reduce render allocations. Throughput: 179K -> 1,721K ops/sec (9.6x vs 6.4.7 release). Allocation: 12,450 -> 1,272 bytes/op (89.8% reduction). Adds benchmark harness and Benchmarks.Baseline project for NuGet package comparison.
197262a to
2dd66ba
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
EventContext allocates heavily per event — 12,450 bytes/op in
v6.4.7 — driven by three ConcurrentDictionaries, a Stopwatch
object, LINQ-based sorting in Render(), and multiple dictionary
iteration passes for key normalization. At high event volumes
this creates significant GC pressure.
Approach
Targeted the allocation and compute hot paths while preserving
the existing thread-safety contract:
ConcurrentDictionarywith flatstring[]/object[]arrays guarded bylock. Linear scanfor key lookup is faster than hash tables at typical field
counts (<20). Insertion order is preserved naturally,
eliminating the sort in
Render().Stopwatchallocation withstatic
Stopwatch.GetTimestamp()arithmetic.TimerCollection,_counts, andPrivateDataare only allocated when actually used.the object isn't shared yet. Internal
SetCorepath avoidsdouble-locking in Render.
separate dictionary scans), ThreadStatic
StringBuilderreuse, cached quote-char strings.
Trade-off:
FindKeyis O(n) vs O(1) hash lookup. For thetypical 10-20 fields per event, the cache-friendly linear scan
is net faster because it avoids Dictionary's allocation and
hash overhead.
Results (.NET 10, 60s sustained)
Review guide
Start with EventContext.cs — the core storage change from
ConcurrentDictionary to flat arrays,
SetCore/FindKey/AppendEntry, and the restructuredRender(). ThenTimerCollection.cs (same ConcurrentDict removal pattern),
AutoTimer.cs (Stopwatch elimination), and
StringExtensions.cs (quote-char caching). The
Benchmarks.Baseline/project is a standalone harness thatreferences the 6.4.7 NuGet package for comparison.
Testing
All 56 existing unit tests pass. Throughput validated via
60-second sustained benchmark runs comparing against the
published 6.4.7 NuGet package baseline.