Skip to content

Enhance performance and memory efficiency in ExcelReader and refactor async processing - #27

Merged
GabrielMarquezMatte merged 3 commits into
masterfrom
develop
Jul 11, 2026
Merged

Enhance performance and memory efficiency in ExcelReader and refactor async processing#27
GabrielMarquezMatte merged 3 commits into
masterfrom
develop

Conversation

@GabrielMarquezMatte

Copy link
Copy Markdown
Owner

This pull request introduces significant optimizations to the CSV and Excel row enumeration code, focusing on reducing allocations and improving performance for both synchronous and asynchronous reading paths. The most important changes are:

Performance and Allocation Optimizations

  • Refactored the core CSV parsing loop in CsvReader.Enumerator to minimize per-field allocations and instance field accesses by introducing a local FieldState struct, and added a fast path for unquoted lines to avoid unnecessary machinery. [1] [2]
  • Improved the async row enumeration in both ExcelEnumerable.AsyncEnumerator and CsvEnumerable.AsyncEnumerator by implementing a fast path that avoids unnecessary state machines for the common case where rows are already buffered, only falling back to async/await when truly needed. [1] [2] [3]

Documentation and Benchmark Updates

  • Updated the README.md to reflect new benchmark results, showing improved performance and reduced allocations for ExcelReader compared to other libraries, and revised the summary analysis to match the new results.

These changes together result in faster CSV/XLSX reading and writing, lower memory allocations, and more accurate documentation of performance characteristics.

GabrielMarquezMatte added 3 commits July 11, 2026 07:46
- Refactored XlsxReader to optimize XML head classification, reducing unnecessary span comparisons.
- Introduced FastDouble for efficient parsing of plain decimal numbers, improving numeric text handling.
- Updated shared string parsing to use pooled byte arrays, minimizing memory allocations.
- Adjusted CsvWriter and SheetWriter to lower flush thresholds, optimizing memory usage during large writes.
- Enhanced cell formatting methods to streamline XML generation for various data types.
- Added tests for FastDouble to ensure accuracy and reliability in parsing.
- Improved benchmarks by pre-sizing MemoryStreams to reduce garbage collection impact.
- Improved performance of MoveNextAsync methods across various readers (ExcelEnumerable, CsvReader, XlsbReader, XlsxReader) by implementing non-async fast paths that handle most cases synchronously, reducing overhead from async state machines.
- Introduced new methods to handle buffer misses more efficiently, allowing for immediate returns when data is available.
- Enhanced Biff12RecordWriter to support fixed record writing with a single reservation for the entire record, optimizing memory usage.
- Updated RowWriter to implement IDisposable, allowing for synchronous disposal and improved resource management.
- Added synchronous counterparts for StartRow in SheetWriter and corresponding extensions to streamline row writing without async overhead.
- Benchmarks updated to measure performance improvements with synchronous row writing methods.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 74.12281% with 118 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.22%. Comparing base (ec31995) to head (de767eb).

Files with missing lines Patch % Lines
...c/ExcelReader.Core/Reader/XlsxReader.Enumerator.cs 68.23% 18 Missing and 9 partials ⚠️
.../ExcelReader.Core/Writer/Internal/CellFormatter.cs 56.25% 11 Missing and 10 partials ⚠️
...c/ExcelReader.Core/Reader/XlsbReader.Enumerator.cs 50.00% 10 Missing and 2 partials ⚠️
...lReader.Core/Writer/Internal/Biff12RecordWriter.cs 60.71% 6 Missing and 5 partials ⚠️
.../ExcelReader.Core/Parser/Internal/CsvEnumerable.cs 44.44% 9 Missing and 1 partial ⚠️
...xcelReader.Core/Parser/Internal/ExcelEnumerable.cs 44.44% 9 Missing and 1 partial ⚠️
...c/ExcelReader.Core/Writer/SheetWriterExtensions.cs 0.00% 9 Missing ⚠️
...rc/ExcelReader.Core/Reader/CsvReader.Enumerator.cs 91.95% 3 Missing and 4 partials ⚠️
src/ExcelReader.Core/Writer/RowWriter.cs 79.31% 6 Missing ⚠️
src/ExcelReader.Core/Writer/SheetWriter.cs 20.00% 4 Missing ⚠️
... and 1 more
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #27      +/-   ##
==========================================
- Coverage   90.79%   89.22%   -1.57%     
==========================================
  Files          76       77       +1     
  Lines        5061     5345     +284     
  Branches      880      956      +76     
==========================================
+ Hits         4595     4769     +174     
- Misses        294      377      +83     
- Partials      172      199      +27     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown

Benchmark Results

Measured on ubuntu-latest (GitHub Actions). Runner noise may affect absolute numbers; use these for relative comparisons within a PR.

ExcelReader.Benchmarks.CsvParseBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
ExcelParserSync 50000 8.323 ms 0.2633 ms 0.0407 ms 1.00 0.01 234.3750 - 3.86 MB 1.00
ExcelParserAsync 50000 7.929 ms 0.0747 ms 0.0194 ms 0.95 0.00 234.3750 - 3.86 MB 1.00
Sep 50000 12.640 ms 0.1131 ms 0.0294 ms 1.52 0.01 234.3750 - 3.87 MB 1.00
Sylvan 50000 17.630 ms 0.4339 ms 0.0671 ms 2.12 0.01 656.2500 62.5000 10.95 MB 2.84
CsvHelperLib 50000 33.012 ms 1.3042 ms 0.3387 ms 3.97 0.04 875.0000 62.5000 14.41 MB 3.73

ExcelReader.Benchmarks.CsvReadBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
ExcelReader 50000 5.531 ms 0.0281 ms 0.0044 ms 1.00 0.00 - - 232 B 1.00
ExcelReaderAsync 50000 5.590 ms 0.0106 ms 0.0027 ms 1.01 0.00 - - 352 B 1.52
Sep 50000 12.105 ms 0.1383 ms 0.0359 ms 2.19 0.01 - - 4024 B 17.34
Sylvan 50000 6.217 ms 0.0537 ms 0.0139 ms 1.12 0.00 93.7500 7.8125 1688701 B 7,278.88
CsvHelperLib 50000 33.386 ms 0.3831 ms 0.0995 ms 6.04 0.02 866.6667 66.6667 15073424 B 64,971.66

ExcelReader.Benchmarks.CsvWriteBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
ExcelReaderWriter 50000 9.148 ms 0.2193 ms 0.0339 ms 1.00 0.00 500.0000 500.0000 500.0000 4 MB 1.00
Sep 50000 9.777 ms 0.0264 ms 0.0069 ms 1.07 0.00 500.0000 500.0000 500.0000 4.01 MB 1.00
SylvanWriter 50000 9.694 ms 0.1427 ms 0.0221 ms 1.06 0.00 500.0000 500.0000 500.0000 4.04 MB 1.01
CsvHelperLib 50000 19.840 ms 1.6328 ms 0.4240 ms 2.17 0.04 1187.5000 656.2500 593.7500 13.79 MB 3.44

ExcelReader.Benchmarks.ParseBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.92GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
ExcelParserSync 50000 19.369 ms 0.0720 ms 0.0111 ms 1.00 0.00 218.7500 3.87 MB 1.00
ExcelParserAsync 50000 21.458 ms 0.3942 ms 0.1024 ms 1.11 0.00 218.7500 3.88 MB 1.00
ExcelParserXlsbSync 50000 8.926 ms 0.1722 ms 0.0447 ms 0.46 0.00 234.3750 3.88 MB 1.00
ExcelParserXlsbAsync 50000 9.569 ms 0.2177 ms 0.0565 ms 0.49 0.00 234.3750 3.88 MB 1.00
MiniExcel 50000 225.128 ms 24.6494 ms 3.8145 ms 11.62 0.18 12000.0000 197.78 MB 51.04
Sylvan 50000 87.956 ms 2.6585 ms 0.4114 ms 4.54 0.02 - 10.48 MB 2.70
SylvanAsync 50000 90.399 ms 1.4946 ms 0.2313 ms 4.67 0.01 - 10.48 MB 2.70

ExcelReader.Benchmarks.ReadBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
ExcelReader 50000 16.831 ms 0.4440 ms 0.0687 ms 1.00 0.01 - 12.49 KB 1.00
ExcelReaderAsync 50000 17.902 ms 0.1080 ms 0.0280 ms 1.06 0.00 - 14.51 KB 1.16
ExcelReaderXlsb 50000 6.890 ms 0.0912 ms 0.0141 ms 0.41 0.00 - 14.23 KB 1.14
ExcelReaderXlsbAsync 50000 6.977 ms 0.1005 ms 0.0156 ms 0.41 0.00 - 16.67 KB 1.33
MiniExcel 50000 211.682 ms 9.2370 ms 1.4294 ms 12.58 0.09 13000.0000 214012.34 KB 17,131.69
Sylvan 50000 53.086 ms 0.7453 ms 0.1153 ms 3.15 0.01 100.0000 1939.18 KB 155.23

ExcelReader.Benchmarks.WriteBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.59GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
ExcelReaderWriter 50000 19.373 ms 0.0603 ms 0.0093 ms 1.00 0.00 500.0000 500.0000 500.0000 4.02 MB 1.00
ExcelReaderWriterSharedStrings 50000 20.305 ms 0.0798 ms 0.0123 ms 1.05 0.00 500.0000 500.0000 500.0000 4.06 MB 1.01
ExcelReaderXlsbWriter 50000 9.487 ms 0.0995 ms 0.0258 ms 0.49 0.00 500.0000 500.0000 500.0000 4.02 MB 1.00
ExcelReaderXlsbWriterSharedStrings 50000 9.260 ms 0.0963 ms 0.0250 ms 0.48 0.00 500.0000 500.0000 500.0000 4.06 MB 1.01
MiniExcel 50000 106.462 ms 5.6236 ms 1.4604 ms 5.50 0.07 5000.0000 1000.0000 1000.0000 84.89 MB 21.13
SpreadCheetah 50000 21.338 ms 0.7765 ms 0.2017 ms 1.10 0.01 1437.5000 718.7500 718.7500 15.84 MB 3.94

ExcelReader.Benchmarks.XlsReadBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.87GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio Gen0 Gen1 Allocated Alloc Ratio
ExcelReader 50000 6.717 ms 0.1966 ms 0.0304 ms 1.00 - - 58.22 KB 1.00
ExcelReaderAsync 50000 6.882 ms 0.1647 ms 0.0428 ms 1.02 - - 58.29 KB 1.00
Sylvan 50000 8.252 ms 0.0229 ms 0.0035 ms 1.23 93.7500 46.8750 1717.73 KB 29.50

ExcelReader.Benchmarks.XlsWriteBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.87GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio Gen0 Gen1 Gen2 Allocated Alloc Ratio
XlsWriter 50000 6.330 ms 0.0359 ms 0.0093 ms 1.00 492.1875 492.1875 492.1875 16.03 MB 1.00
XlsxWriter 50000 19.125 ms 0.0499 ms 0.0077 ms 3.02 500.0000 500.0000 500.0000 4.02 MB 0.25

@GabrielMarquezMatte
GabrielMarquezMatte merged commit ac94393 into master Jul 11, 2026
12 checks passed
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