2-30 倍性能:Acl.Excel 设计哲学与零依赖

2-30 倍性能:Acl.Excel 设计哲学与零依赖

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

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

本系列第 4 篇 / 共 11 篇(Acl.Excel 深度解析系列第 4 篇,技术内幕第 1 篇)

前情回顾:

系列导航下一篇:读取引擎:百万行如何恒定内存 →

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

核心结论(TL;DR)

  • 零第三方依赖:仅依赖 .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 实测 百万行读写对比
概述与设计哲学:2-30 倍性能与零依赖 微内核架构、4 原语、双策略正交(本文)
读取引擎:百万行如何恒定内存 四层流式管线、4MB 滑动窗口
零装箱设计:24B CellValue 值类型联合体清零上亿次装箱
写入引擎:字节直写 分配降 63%、并行写入、原子替换
DEFLATE 引擎:2916 行移植 纯 C# 移植 libdeflate、14 轮优化
安全设计:8 层防御 解压炸弹/XXE/ZipSlip 全链路护栏
性能基准与跨库对比 读取最高快 32.8 倍、内存 1/359
实战指南:八大场景与避坑 生产落地、调优清单、选型表

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

posted @ 2026-08-05 16:50  风云  阅读(2)  评论(0)    收藏  举报