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

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

获取方式:Acl.Excel 已发布到 NuGet,安装命令 dotnet add package Acl.Excel(当前稳定版 4.0.1)。

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

序号文章重点
从 libdeflate 到纯 C#:Acl.Excel DEFLATE 解压/压缩器的移植与优化之旅纯 C# 移植 libdeflate
从 libdeflate 到纯 C#(续):优化纯 C# DEFLATE 压缩器——从 1.6x 到 4.9x 的提速之路14 轮优化提速
Acl.Excel vs MiniExcel 1.45.0:性能对比与选型分析(.NET 8 基准)百万行对等实测
不依赖 NPOI/ClosedXML,纯 C# Excel 库如何做到 30 倍性能?概述与设计哲学(本文)
百万行Excel如何恒定内存?Acl.Excel流式拆解读取引擎流式管线
24B消灭上亿次装箱:Acl.Excel零装箱设计24B CellValue 零装箱
写入分配砍掉 63%:Acl.Excel 绕过 XmlWriter 字节直写字节直写与并行写入
零依赖纯 C# DEFLATE 引擎:2916 行移植 libdeflateDEFLATE 引擎移植
一个 42KB 的 Excel 如何撑爆 4.5PB?Acl.Excel 8 层防御安全纵深防御
Acl.Excel 跨库基准:读取最高快 32.8 倍跨库性能基准
从实战到生产:Acl.Excel 八大场景与避坑指南(终篇)实战与避坑指南

建议按 ①→⑪ 顺序阅读,从底层算法到实战落地形成完整认知。(当前本篇为第 4 篇,已以 粗体 标记。)

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

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

处理 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 与性能维度,富样式、图表、公式引擎不在讨论范围。以上局限已在正文相应位置如实标注,供读者结合完整信息自行判断。

posted @ 2026-08-11 18:48  风云  阅读(312)  评论(0)    收藏  举报