不依赖 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 行移植 libdeflate DEFLATE 引擎移植 ⑨ 一个 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 压缩)
所有扩展方法(QueryAdaptive、QueryRaw、QueryDynamic、Fill 等)都基于这 4 个原语构建,不侵入核心。这种设计的好处是:核心接口极度稳定,性能优化集中在原语内部,不影响上层 API;扩展方法可以灵活增加而不引入破坏性变更。
三、读取管线:从字节到模型
读取一条数据的完整路径是:
- ZIP 层:
LibDeflateDecompressor(约 .NET DeflateStream 的 2 倍吞吐)直接解压 worksheet XML 条目 - XML 扫描层:
SheetRowScanner按ReadStrategy选择扫描器:ByteSheetScanner(全量字节扫描,零分配)、ByteSheetScanner2(4MB 滑动窗口,恒定内存)或XmlReaderSheetScanner(标准 XmlReader 流式) - 单元格解码层:
CellDecoder将 XML 字符串解码为CellValue(24B 值类型结构体,11 种类型判别) - 映射层:
CellSetter<TModel>(表达式树编译或源生成器预编译)将CellValue[]直接赋值到模型属性,消除中间object[]缓冲
四、写入管线:从模型到文件
写入路径同样极致优化:
- 映射层:
CompiledRowMapper将模型属性值编译为CellValue[]写入缓冲区 - XML 写入层:
Utf8RawWriter直接写入 UTF-8 字节,绕过XmlWriter和StreamWriter的多层分配。所有固定 XML 结构预编译为byte[]常量 - 压缩层:
IZipEntrySink按CompressionStrategy分发:Throughput(.NET 内置)、Balanced(libdeflate 均衡)、Compression(libdeflate 最高压缩比) - 持久化层:多 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 与性能维度,富样式、图表、公式引擎不在讨论范围。以上局限已在正文相应位置如实标注,供读者结合完整信息自行判断。

浙公网安备 33010602011771号