Repository navigation
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d9c75d62-dda8-4115-be1b-ef2f101a6d61
|
@EgorBot -amd -osx_arm64 using System.Formats.Cbor;
using BenchmarkDotNet.Attributes;
[MemoryDiagnoser]
public class CborReaderMapBenchmarks
{
private byte[] _onePair = null!;
private byte[] _larger = null!;
private byte[] _canonical = null!;
private byte[] _nested = null!;
private static readonly byte[] s_duplicate = new byte[] { 0xa2, 0x01, 0x00, 0x01, 0x00 };
private CborReader _reused = null!;
[GlobalSetup]
public void Setup()
{
_onePair = EncodeIntMap(CborConformanceMode.Strict, pairs: 1);
_larger = EncodeIntMap(CborConformanceMode.Strict, pairs: 32);
_canonical = EncodeIntMap(CborConformanceMode.Canonical, pairs: 32);
_nested = EncodeNested(CborConformanceMode.Strict, depth: 4, keysPerLevel: 2);
_reused = new CborReader(_onePair, CborConformanceMode.Strict);
}
[Benchmark(Baseline = true)]
public int OnePair_Strict_Fresh() => ReadValue(new CborReader(_onePair, CborConformanceMode.Strict));
[Benchmark]
public int OnePair_Strict_Reused()
{
_reused.Reset(_onePair);
return ReadValue(_reused);
}
[Benchmark]
public int Larger_Strict() => ReadValue(new CborReader(_larger, CborConformanceMode.Strict));
[Benchmark]
public int Canonical_Sorted() => ReadValue(new CborReader(_canonical, CborConformanceMode.Canonical));
[Benchmark]
public int Nested_Strict() => ReadValue(new CborReader(_nested, CborConformanceMode.Strict));
[Benchmark]
public int Duplicate_Strict()
{
var reader = new CborReader(s_duplicate, CborConformanceMode.Strict);
try { return ReadValue(reader); }
catch (CborContentException) { return -1; }
}
[Benchmark]
public int Rollback_Strict()
{
var reader = new CborReader(s_duplicate, CborConformanceMode.Strict);
try { reader.SkipValue(); return 0; }
catch (CborContentException) { return -1; }
}
private static int ReadValue(CborReader reader)
{
if (reader.PeekState() == CborReaderState.StartMap)
{
int count = reader.ReadStartMap()!.Value;
int sum = 0;
for (int i = 0; i < count; i++)
{
sum += ReadValue(reader);
sum += ReadValue(reader);
}
reader.ReadEndMap();
return sum;
}
return reader.ReadInt32();
}
private static byte[] EncodeIntMap(CborConformanceMode mode, int pairs)
{
var writer = new CborWriter(mode);
writer.WriteStartMap(pairs);
for (int i = 0; i < pairs; i++)
{
writer.WriteInt32(i);
writer.WriteInt32(i);
}
writer.WriteEndMap();
return writer.Encode();
}
private static byte[] EncodeNested(CborConformanceMode mode, int depth, int keysPerLevel)
{
var writer = new CborWriter(mode);
WriteNested(writer, depth, keysPerLevel);
return writer.Encode();
}
private static void WriteNested(CborWriter writer, int depth, int keysPerLevel)
{
if (depth == 0)
{
writer.WriteInt32(0);
return;
}
writer.WriteStartMap(keysPerLevel);
for (int i = 0; i < keysPerLevel; i++)
{
writer.WriteInt32(i);
WriteNested(writer, depth - 1, keysPerLevel);
}
writer.WriteEndMap();
}
} |
|
Azure Pipelines: Successfully started running 3 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. Some pipeline(s) encountered errors during trigger evaluation. |
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Optimizes strict-mode CBOR map key uniqueness validation by deferring the HashSet<(int,int)> allocation until a second key is encountered, avoiding allocations for single-entry strict maps.
Changes:
- Avoid allocating/renting the key-tracking
HashSeton the first strict map key by stashing the first key’s encoding range in_previousKeyEncodingRange. - Allocate and seed the
HashSetonly when a second key is read, then proceed with duplicate detection as before.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Map.cs | Defers strict-mode key-uniqueness HashSet creation until the second key and seeds it with the first key’s range. |
Copilot's findings
- Files reviewed: 1/1 changed files
- Comments generated: 1
| HashSet<(int Offset, int Length)>? keyEncodingRanges = _keyEncodingRanges; | ||
| if (keyEncodingRanges is null) | ||
| { | ||
| if (_previousKeyEncodingRange is null) | ||
| { | ||
| _previousKeyEncodingRange = currentKeyEncodingRange; | ||
| return; | ||
| } | ||
|
|
||
| keyEncodingRanges = GetKeyEncodingRanges(); | ||
| keyEncodingRanges.Add(_previousKeyEncodingRange.Value); | ||
| } |
|
Tagging subscribers to this area: @dotnet/area-system-formats-cbor, @bartonjs, @vcsjones |
|
Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it. |
Defers the
HashSet<(int,int)>allocation used for strict-mode map key-uniqueness validation until a second key is actually encountered. Single-pair strict maps (a common shape) no longer allocate the set at all.What changed
CborReader.ValidateKeyUniquenesspreviously allocated (or rented) the key-encodingHashSeton the very first key of every strict-mode map. Now the first key's encoding range is stashed in the existing_previousKeyEncodingRangeslot; the set is only allocated on the second key, seeded with the stashed first range before the duplicate check proceeds. Behavior is otherwise identical — duplicate detection, buffer reset on throw, checkpoint rollback, and nested/pooled state are all unchanged.The
_previousKeyEncodingRangefield already exists and is used by the canonical-mode sorted-key path. Strict and canonical validation are mutually exclusive for a given reader (fixed byConformanceMode), so the two never share the slot concurrently.Results
Local BenchmarkDotNet A/B (macOS arm64, .NET 11.0.0, BDN 0.14.0,
[MemoryDiagnoser]), same source except this one file:OnePair_Strict_FreshOnePair_Strict_ReusedLarger_Strict(32 pairs)Canonical_SortedNested_StrictDuplicate_StrictRollback_StrictOne-pair strict maps drop from 712 B → 424 B (−40%), now matching the canonical path that never needed the set. All multi-key/canonical/nested/duplicate/rollback controls are byte-identical (no regression); the pooled reused-reader path stays at 0 B.
Full System.Formats.Cbor test suite passes locally (1921 total, 0 failed, 0 skipped).
Note
This PR description was drafted with the assistance of GitHub Copilot.