Skip to content

Optimize TOML parsing - #111

Merged
prozolic merged 3 commits into
mainfrom
optimize
Jul 25, 2026
Merged

Optimize TOML parsing#111
prozolic merged 3 commits into
mainfrom
optimize

Conversation

@prozolic

Copy link
Copy Markdown
Owner

This PR optimize TOML parsing in CsTomlSerializer.Deserialize<TomlDocument>.
This Change reduces memory allocation in main/common use cases.

Changed

  • Estimate the initial capacity of TomlTableNodeDictionary from key/value pairs
  • Reuse duplicated TomlDottedKey in TomlTableNode
  • Change from switch processing to custom bitMask(ulong) in ReadUnquotedString/ReadArray

Benchmark

Method Job BuildConfiguration Toolchain Mean Error StdDev Gen0 Gen1 Allocated
CsToml_Parse_FromUTF16 Benchmark.NET 10.0 Default .NET 10.0 28.34 us 1.191 us 1.782 us 3.6926 0.3357 30.41 KB
CsToml_Parse_FromUTF8 Benchmark.NET 10.0 Default .NET 10.0 23.35 us 1.406 us 1.925 us 3.2959 0.3052 26.99 KB
Tommy_Parse Benchmark.NET 10.0 Default .NET 10.0 78.13 us 3.639 us 5.334 us 11.4746 0.9766 93.81 KB
Tomlet_Parse Benchmark.NET 10.0 Default .NET 10.0 547.19 us 30.420 us 44.590 us 33.2031 2.9297 273.86 KB
Tomlyn_Parse Benchmark.NET 10.0 Default .NET 10.0 163.38 us 8.868 us 13.273 us 3.4180 0.2441 29.6 KB
CsToml_Parse_FromUTF16 Benchmark.NET 8.0 Default .NET 8.0 35.69 us 2.352 us 3.447 us 3.6621 0.3052 30.41 KB
CsToml_Parse_FromUTF8 Benchmark.NET 8.0 Default .NET 8.0 30.83 us 1.647 us 2.414 us 3.2959 0.3052 26.99 KB
Tommy_Parse Benchmark.NET 8.0 Default .NET 8.0 92.53 us 4.266 us 6.252 us 11.4746 0.9766 94.19 KB
Tomlet_Parse Benchmark.NET 8.0 Default .NET 8.0 695.67 us 46.870 us 70.152 us 34.1797 2.9297 285.36 KB
Tomlyn_Parse Benchmark.NET 8.0 Default .NET 8.0 190.40 us 13.238 us 19.404 us 2.9297 - 29.6 KB
CsToml_Parse_FromUTF16 Benchmark.NET 9.0 Default .NET 9.0 30.00 us 2.265 us 3.390 us 3.6926 0.3357 30.41 KB
CsToml_Parse_FromUTF8 Benchmark.NET 9.0 Default .NET 9.0 27.92 us 1.345 us 1.972 us 3.2959 0.3052 26.99 KB
Tommy_Parse Benchmark.NET 9.0 Default .NET 9.0 87.03 us 3.581 us 5.360 us 11.4746 0.9766 94.19 KB
Tomlet_Parse Benchmark.NET 9.0 Default .NET 9.0 617.88 us 20.606 us 29.552 us 34.1797 2.9297 285.34 KB
Tomlyn_Parse Benchmark.NET 9.0 Default .NET 9.0 167.09 us 7.494 us 10.985 us 3.4180 0.2441 29.6 KB
CsToml_Parse_FromUTF16 NuGetCsToml NuGetCsToml .NET 10.0 31.13 us 2.442 us 3.503 us 3.9978 0.3967 32.71 KB
CsToml_Parse_FromUTF8 NuGetCsToml NuGetCsToml .NET 10.0 29.89 us 1.962 us 2.937 us 3.5706 0.3357 29.3 KB
Tommy_Parse NuGetCsToml NuGetCsToml .NET 10.0 98.27 us 5.933 us 8.697 us 11.4746 0.9766 93.81 KB
Tomlet_Parse NuGetCsToml NuGetCsToml .NET 10.0 587.74 us 42.267 us 63.263 us 33.2031 2.9297 273.86 KB
Tomlyn_Parse NuGetCsToml NuGetCsToml .NET 10.0 159.71 us 8.261 us 12.108 us 2.9297 - 29.6 KB

Copilot AI review requested due to automatic review settings July 25, 2026 12:03

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.

Pull request overview

This PR focuses on reducing allocations and improving throughput in the core TOML parsing path for CsTomlSerializer.Deserialize<TomlDocument>, mainly by pre-sizing table dictionaries, reusing parsed key instances, and tightening hot-path parsing loops.

Changes:

  • Add a best-effort key/value count estimator to pre-size TomlTableNodeDictionary and reduce rehash/reserve churn during document parse.
  • Reuse previously-parsed TomlDottedKey instances via TomlTableNodeHolder/mirror-node lookup to avoid repeated key parsing/allocation.
  • Optimize array scanning and key parsing hot paths in CsTomlReader (bitmask classification, reduced branching) and add a multi-segment array regression test.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/CsToml.Tests/ArrayReadMultiSegmentTest.cs Adds coverage for ReadOnlySequence<byte> multi-segment array parsing edge cases.
src/CsToml/Values/TomlTableNodeHolder.cs Introduces a small holder to enable key reuse from existing table nodes.
src/CsToml/Values/TomlTableNodeDictionary.cs Adds initial capacity estimation support and enables returning the stored key instance.
src/CsToml/Values/TomlTableNode.cs Adds capacity reservation and key/mirror lookup helpers to support key reuse.
src/CsToml/Values/TomlTable.GetValue.cs Removes an unused using.
src/CsToml/Values/TomlDottedKey.cs Removes unused error namespace import.
src/CsToml/TomlDocument.cs Wires key reuse + capacity estimation into the document parse loop.
src/CsToml/Formatter/PrimitiveObjectFormatter.cs Switches type-jump table to FrozenDictionary for faster lookups / fewer allocations.
src/CsToml/Formatter/EnumFormatter.cs Switches enum lookup tables to FrozenDictionary and refactors static initialization.
src/CsToml/CsTomlReader.cs Adds key-count estimation and refactors key/array parsing hot paths.
src/CsToml/CsTomlParser.cs Updates call site for the new generic TrySkipIfNewLine API.
Comments suppressed due to low confidence (6)

src/CsToml/Values/TomlTableNodeDictionary.cs:207

  • The bounds check in TryGetValueCore can allow index == entries.Length, which would throw IndexOutOfRangeException when accessing entries[index]. Use >= to reject that case (and still handle -1 via the uint cast).
            if ((uint)index > (uint)entries.Length)

src/CsToml/CsTomlReader.cs:946

  • Remove the commented-out return; the method already returns via nodeHolder.GetKey and the commented code is dead.
            return nodeHolder.GetKey<T>(bufferWriter.WrittenSpan);
            //return T.Parse(bufferWriter.WrittenSpan);

src/CsToml/CsTomlReader.cs:1182

  • Remove the commented-out return; it’s dead code and makes the intended behavior ambiguous.
            return nodeHolder.GetKey<T>(unreadSpan[..totalLength]);
            //return T.Parse(unreadSpan[..totalLength]);

src/CsToml/CsTomlReader.cs:1191

  • Remove the commented-out return; it’s dead code and makes the intended behavior ambiguous.
            return nodeHolder.GetKey<T>(bufferWriter!.WrittenSpan);
            //return T.Parse(bufferWriter!.WrittenSpan);

src/CsToml/CsTomlReader.cs:1385

  • Remove the commented-out constructor call; nodeHolder.GetKey is now the single intended path.
            return nodeHolder.GetKey<TomlUnquotedDottedKey>(unreadSpan[..totalLength]);
            //return new TomlUnquotedDottedKey(unreadSpan[..totalLength]);

src/CsToml/CsTomlReader.cs:1390

  • Remove the commented-out constructor call; nodeHolder.GetKey is now the single intended path.
            return nodeHolder.GetKey<TomlUnquotedDottedKey>(bufferWriter!.WrittenSpan);
            //return new TomlUnquotedDottedKey(bufferWriter!.WrittenSpan);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/CsToml/Values/TomlTableNodeDictionary.cs
Comment thread src/CsToml/CsTomlReader.cs
Comment thread src/CsToml/Values/TomlTableNodeHolder.cs Outdated
@prozolic
prozolic merged commit 7b35c7a into main Jul 25, 2026
1 check passed
@prozolic
prozolic deleted the optimize branch July 25, 2026 12:19
@prozolic prozolic mentioned this pull request Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants