不依赖 NPOI/ClosedXML,纯 C# Excel 库如何做到 30 倍性能?

不依赖 NPOI/ClosedXML,纯 C# Excel 库如何做到 30 倍性能?

本文力求客观:既呈现 Acl.Excel 的实测数据,也如实标注测试口径、版本差异与已知局限,供读者自行判断。

口径说明:本文性能数据基于 10K 行 × 7 列基准(.NET 8 Release、BenchmarkDotNet 实测);局限:测试规模非百万行级,且未逐项对齐各竞品最新版本。

Excel系列文章 第4篇 / 共11篇

系列导航:下一篇:读取引擎 →

前情回顾:

  • 第 1 篇:从零实现 DEFLATE 压缩器:纯 C# 移植版如何在 Excel 场景超越 .NET 内置?
  • 第 2 篇:优化纯 C# DEFLATE 压缩器:从 0 到超越 .NET 内置的完整历程
  • 第 3 篇:Acl.Excel vs MiniExcel 1.45.0:百万行读写实测,托管内存低至 1/37
  • 第 4 篇(本文):不依赖 NPOI/ClosedXML,纯 C# Excel 库如何做到 30 倍性能?

处理 Excel 文件,.NET 开发者常常陷入两难:选择功能全面的重量级库(ClosedXML、EPPlus),就要承受内存膨胀与性能损耗;选择轻量方案(MiniExcel),又不得不在性能与灵活性之间折中。Acl.Excel 给出了第三条路------从 ZIP 解压、XML 解析、DEFLATE 压缩到类型映射,全链路从零自建:零第三方依赖、纯托管,在 10K 行 × 7 列基准下实现 2-30 倍的性能领先。

核心结论

  • 零第三方依赖:仅依赖 .NET 运行时内置组件(System.IO.Compression、System.Xml),不引入 NPOI、EPPlus、ClosedXML,亦无原生 P/Invoke。
  • 2-30 倍性能领先:10K 行 × 7 列基准下,写入快于 MiniExcel 2.1 倍、ClosedXML 13.0 倍、NPOI 13.8 倍;读取快于 MiniExcel 5.6 倍、NPOI 23.1 倍、ClosedXML 32.8 倍。
  • 流式优先、恒定内存:单遍流式扫描 + yield return 延迟消费 + ArrayPool 缓冲复用,内存不随数据量线性增长。
  • 微内核 4 原语IWorkbook 仅暴露 3 读 1 写共 4 个原语,扩展能力全部构建于原语之上,核心接口极度稳定。
  • 双策略正交:读侧(ReadStrategy / DecompressionStrategy)与写侧(CompressionStrategy / SharedStringsMode)独立组合、互不耦合。

一、核心设计目标

Acl.Excel 的设计并非对标某个特定库,而是围绕三个核心约束展开:

零第三方依赖。整个库仅依赖 .NET 运行时内置组件(System.IO.Compression、System.Xml),不引入 NPOI、EPPlus、ClosedXML 或任何原生 P/Invoke。这意味着部署时无需携带额外 DLL,也无需担心平台兼容性。

流式优先,恒定内存。无论是读取百万行还是写入千万行数据,内存占用都应保持在一个可控范围内,不随数据量线性增长。Acl.Excel 通过单遍流式扫描、yield return 延迟消费、ArrayPool 缓冲复用实现这一目标。

全链路性能优化。从 ZIP 层(自研 LibDeflate 压缩/解压引擎)到 XML 层(ByteSheetScanner 字节扫描器、Utf8RawWriter 字节直写)再到映射层(表达式树编译、源生成器),每个环节都经过 BenchmarkDotNet 基准测试验证。

二、微内核架构

Acl.Excel 采用微内核 + 扩展方法 的架构设计。IWorkbook 接口仅暴露 4 个:

读取原语(3 个) : - Query<TModel>(options) --- 泛型查询,返回 IEnumerable<TModel> - QueryCellsInto(options, buffer) --- 原始行读取,零装箱,返回 IEnumerable<CellValue[]> - OpenDataReader(options) --- 前向 DbDataReader,零装箱

写入原语(1 个) : - Write<TModel>(data, sheet, builder, append) --- 泛型写入

复制代码
调用层 (Public API)
  ExcelFile (静态门面)  |  IWorkbook (微内核)  |  FluentQuery (流式查询)
                          |
       ┌──────────────────┼──────────────────┐
       ▼                  ▼                  ▼
   读取引擎           写入引擎           映射引擎
   SheetRowScanner   XlsxWriter         CellSetter
   ├─ ByteSheetScanner (Fast)          CompiledRowMapper
   ├─ ByteSheetScanner2 (SlidingWindow)
   └─ XmlReaderSheetScanner (Standard)
       │                  │                  │
       ▼                  ▼                  ▼
   CellDecoder       Utf8RawWriter      CellValue
   (零装箱单元格解码)  (字节直写)         (24B 值类型)
       │                  │
       ▼                  ▼
   LibDeflateDecompressor   LibDeflateCompressor
   (纯 C# DEFLATE 解压)     (纯 C# DEFLATE 压缩)

所有扩展方法(QueryAdaptiveQueryRawQueryDynamicFill 等)都基于这 4 个原语构建,不侵入核心。这种设计的好处是:核心接口极度稳定,性能优化集中在原语内部,不影响上层 API;扩展方法可以灵活增加而不引入破坏性变更。

三、读取管线:从字节到模型

读取一条数据的完整路径是:

  1. ZIP 层LibDeflateDecompressor(约 .NET DeflateStream 的 2 倍吞吐)直接解压 worksheet XML 条目
  2. XML 扫描层SheetRowScannerReadStrategy 选择扫描器 ------ ByteSheetScanner(全量字节扫描,零分配)、ByteSheetScanner2(4MB 滑动窗口,恒定内存)或 XmlReaderSheetScanner(标准 XmlReader 流式)
  3. 单元格解码层CellDecoder 将 XML 字符串解码为 CellValue(24B 值类型结构体,11 种类型判别)
  4. 映射层CellSetter<TModel>(表达式树编译或源生成器预编译)将 CellValue[] 直接赋值到模型属性,消除中间 object[] 缓冲

四、写入管线:从模型到文件

写入路径同样极致优化:

  1. 映射层CompiledRowMapper 将模型属性值编译为 CellValue[] 写入缓冲区
  2. XML 写入层Utf8RawWriter 直接写入 UTF-8 字节,绕过 XmlWriterStreamWriter 的多层分配。所有固定 XML 结构预编译为 byte[] 常量
  3. 压缩层IZipEntrySinkCompressionStrategy 分发 ------ Throughput(.NET 内置)、Balanced(libdeflate 均衡)、Compression(libdeflate 最高压缩比)
  4. 持久化层 :多 Sheet 并行写入(Parallel.For),先写 .tmp 文件,成功后原子替换

五、双策略正交控制面

Acl.Excel 的读侧和写侧由两个独立策略控制:

维度 策略 选项
读侧解析 ReadStrategy Standard(XmlReader)/ Fast(ByteSheetScanner v1)/ SlidingWindow(ByteSheetScanner2 v2)
读侧解压 DecompressionStrategy Deflate(.NET 内置)/ LibDeflate(纯托管,默认)
写侧压缩 CompressionStrategy Throughput(默认)/ Balanced / Compression
写侧字符串 SharedStringsMode Inline(默认)/ Shared / Auto(自适应 30% 重复率阈值)

两组策略正交、互不耦合。读侧选择不影响写侧,反之亦然。这让开发者可以按场景灵活组合:例如,超大文件读取可以选 SlidingWindow + LibDeflate(恒定 4MB RSS),而小文件快速读取可以选 Fast + LibDeflate(全量入内存,极致吞吐)。

六、关键数据:跨库性能对比

在 10K 行 x 7 列的基准测试中(.NET 8 Release,BenchmarkDotNet 实测):

写入: | 库 | 耗时 | 分配 | |----|------|------| | Acl.Excel | 13.35 ms | 462 KB | | MiniExcel | 27.49 ms (2.1x) | 43,498 KB (94x) | | ClosedXML | 173.27 ms (13.0x) | 165,895 KB (359x) | | NPOI | 184.72 ms (13.8x) | 108,165 KB (234x) |

读取: | 库 | 耗时 | 分配 | |----|------|------| | Acl.Excel | 10.84 ms | 2,350 KB | | MiniExcel | 60.49 ms (5.6x) | 57,788 KB (24.6x) | | NPOI | 250.91 ms (23.1x) | 111,192 KB (47.3x) | | ClosedXML | 355.36 ms (32.8x) | 254,542 KB (108x) |

七、适用场景与边界

Acl.Excel 专为高吞吐、低内存、大批量数据处理场景设计:ETL 管道、报表生成、日志导入导出、数据迁移。它不适合需要随机单元格级读写、富样式、图表或公式引擎的场景 ------ 这些场景更适合 EPPlus 或 ClosedXML 等内存工作簿库。

免责声明:本文基于 Acl.Excel 4.0.0(2026 年 7 月撰写日期前后实测)在 10K 行 × 7 列基准(.NET 8 Release、BenchmarkDotNet)下的实测数据撰写,测试环境与口径见正文;数据可能随版本演进变化,重要决策请自行复测核验。

客观性说明:本文的结论基于以下事实约束,而非自诩无偏------① 性能数据来自单一基准(10K 行 × 7 列、.NET 8 Release、BenchmarkDotNet 实测),未覆盖百万行级规模;② 竞品对比未逐项对齐各库最新版本,倍率数字以正文表格为准;③ 本文聚焦核心读写 API 与性能维度,富样式、图表、公式引擎不在讨论范围。以上局限已在正文相应位置如实标注,供读者结合完整信息自行判断。

系列导航

Acl.Excel 系列文章(共 11 篇)

序号 文章 重点
从零实现 DEFLATE 压缩器 纯 C# 移植 libdeflate
优化 DEFLATE 压缩器 14 轮优化超越 .NET 内置
Acl.Excel vs MiniExcel 实测 百万行读写对比
不依赖 NPOI/ClosedXML,纯 C# Excel 库如何做到 30 倍性能? 微内核架构、4 原语、双策略正交(本文)
读取引擎:百万行如何恒定内存 四层流式管线、4MB 滑动窗口
零装箱设计:24B CellValue 值类型联合体清零上亿次装箱
写入引擎:字节直写 分配降 63%、并行写入、原子替换
DEFLATE 引擎:2916 行移植 纯 C# 移植 libdeflate、14 轮优化
安全设计:8 层防御 解压炸弹/XXE/ZipSlip 全链路护栏
性能基准与跨库对比 读取最高快 32.8 倍、内存 1/359
实战指南:八大场景与避坑 生产落地、调优清单、选型表

建议按 ①→⑪ 顺序阅读,第 ①-③ 篇为 DEFLATE 引擎与性能对比,第 ④-⑪ 篇为 Acl.Excel 技术内幕深度解析。(当前本篇为第 4 篇,已以 粗体 标记。)