Acl.Excel 跨库基准:读取最高快 32.8 倍

Acl.Excel 跨库基准:读取最高快 32.8 倍

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

口径说明:本机实测(单一机器、单轮次),运行于 .NET 8/10 Release,使用 BenchmarkDotNet 0.15.8 测量;Acl.Excel 4.0.0 为基线,对照 MiniExcel 1.45.0、ClosedXML 0.104.0、NPOI 2.7.2 等(竞品版本以正文各表为准);百万行级对等测试数据来自 MiniExcel 官方 benchmark 仓库。已知局限:未做跨机器复现与长期统计。

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

系列导航← 上一篇:安全设计 · 下一篇:实战指南 →

本系列第七篇呈现 Acl.Excel 的完整性能基准测试数据,包括与 MiniExcel、ClosedXML、NPOI 的跨库对比,以及压缩引擎、字节扫描器、写入引擎各环节的独立基准数据。所有测试基于 BenchmarkDotNet 实测,可复现。

读取同一份 10,000 行 x 7 列的 xlsx,ClosedXML 用了 355.36 ms,Acl.Excel 用了 10.84 ms,快 32.8 倍。更值得注意的是内存:写入路径上 Acl.Excel 只分配了 462 KB,而 ClosedXML 分配了 165,895 KB,相差 359 倍。在一个每秒要导出几十份报表的 API 服务里,这个差距不只是"快一点",而是 GC 会不会成为瓶颈的分水岭。

本文把 Acl.Excel 的性能画像完整摊开:从跨库横评、百万行级对等测试,到写入引擎、压缩器、解压器、字节扫描器四个微观组件的独立基准。读完你会知道每一个数字是怎么来的,以及在你自己的场景里该选哪套策略。所有测试在 .NET 8/10 Release 配置下运行,使用 BenchmarkDotNet 0.15+ 进行测量。

核心结论
- 10K 行 x 7 列场景,写入比 MiniExcel 快约 2 倍、比 ClosedXML/NPOI 快一个数量级;读取比 MiniExcel 快 5.6 倍、比 ClosedXML 快 32.8 倍。
- 内存分配是更大的差距:写入分配仅为 MiniExcel 的 1/94、ClosedXML 的 1/359,Gen0 回收次数为 0。
- 百万行级对等测试(SlidingWindow 4MB)中,全量读取 1.88x、写入 2.84x 快于 MiniExcel,分配分别降至 1/5.8 与 1/37.5。
- 纯托管的 LibDeflate 解压在典型 Excel 体量(100KB-10MB)稳定拿到 1.8x-2.7x,代价是零 P/Invoke、零原生依赖。
- 压缩档位可按场景取舍:L1 压缩比 3.15 换 2.1x 速度,L9 压缩比 4.05 但速度降到 0.6x。

一、跨库对比:同一份数据,四个库各跑一遍

1.1 先说清楚测试怎么跑的

性能数字脱离测试条件就没有意义,先把环境交代清楚。

参数
运行时 .NET 8.0 / .NET 10.0
配置 Release
基准工具 BenchmarkDotNet 0.15.8
数据规模 10,000 行 x 7 列
数据类型 混合(字符串、整型、浮点、日期)

数据类型刻意选了混合型,而不是清一色的字符串或数值:后者容易让某些库的特化路径吃到不该吃的红利,与真实业务表格的形态也不符。

1.2 写入性能:13.35 ms 与 462 KB

版本 Mean Allocated Gen0
Acl.Excel 4.0.0 13.35 ms 462 KB
MiniExcel 1.45.0 27.49 ms 43,498 KB 1700
ClosedXML 0.104.0 173.27 ms 165,895 KB 3000
NPOI 2.7.2 184.72 ms 108,165 KB 2000

耗时上的领先是可预期的,真正拉开身位的是 Allocated 那一列。

Acl.Excel 在写入路径上的分配仅为 MiniExcel 的 1/94,ClosedXML 的 1/359。这意味着在 GC 压力敏感的场景(如高频 API 服务)中,Acl.Excel 的 GC 暂停时间远低于其他库。Gen0 一列的 "—" 也值得留意:整个基准跑完没有触发 Gen0 回收,而其他三个库的 Gen0 次数在 1700~3000 之间。

1.3 读取性能:差距被进一步拉开

版本 Mean Allocated Gen0
Acl.Excel 4.0.0 10.84 ms 2,350 KB
MiniExcel 1.45.0 60.49 ms 57,788 KB 1900
NPOI 2.7.2 250.91 ms 111,192 KB 2800
ClosedXML 0.104.0 355.36 ms 254,542 KB 5000

读取路径的差距比写入更大:Acl.Excel 比 MiniExcel 快 5.6 倍,比 ClosedXML 快 32.8 倍。这主要归功于字节扫描器(绕过 XmlReader)和 LibDeflate 解压(绕过 DeflateStream)的双重优化。

换句话说,读取之所以领先更多,不是因为多做了什么,而是因为少做了两层:不必把字节流先解成字符流再交给 XmlReader,也不必让解压走 DeflateStream 的托管封装。

二、百万行级基准:内存恒定条件下的对等较量

10K 行只能说明常规业务表格的表现。真正考验架构的是百万行级,那也是最容易"跑得快但内存爆掉"的量级。

在 100 万行 x 10 列的基准测试中(使用 MiniExcel 官方仓库的测试数据):

操作 Mean Allocated
Query(全量读取) Acl.Excel 1.88x 快于 MiniExcel 1/5.8
QueryFirst(首行) Acl.Excel 81µs vs 85µs(持平)
Create(写入) Acl.Excel 2.84x 快于 MiniExcel 1/37.5

Acl.Excel 使用 SlidingWindow 策略(4MB 滑动窗口),与 MiniExcel 的流式读取条件对等对比。QueryFirst 仅取首行,Acl.Excel 和 MiniExcel 在微秒级几乎持平,说明两者的首行读取开销接近。

这里的公平性值得强调一句:SlidingWindow 并不是 Acl.Excel 最快的读取策略,但它是唯一能与 MiniExcel 流式读取形成对等条件的策略。用更慢的那一档去比,结论才站得住。

三、拆开看写入引擎:Utf8RawWriter 省在哪

3.1 Utf8RawWriter vs StreamWriter + XmlWriter

数据规模 端到端 Utf8RawWriter 旧 XmlWriter 分配降幅
10K x 7 39ms 38.66ms 39.23ms 63%
100K x 7(数值) 202ms 110ms 173ms
100K x 7(文本) 197ms 123ms 254ms
100K x 7(混合) 117ms 57ms 180ms

数据量越大,Utf8RawWriter 的优势越明显。文本为主的场景端到端耗时降幅最大(-51.7%),因为 XmlWriter 在文本场景下字符串分配开销最高。

10K 规模下两者耗时几乎一样,但分配已经降了 63%,这说明小数据量时瓶颈不在写入器本身;等规模上到 100K,省下来的分配才开始转化为可见的时间收益。

3.2 压缩策略对比

写入速度和文件大小是一对此消彼长的取舍,Acl.Excel 把选择权交给调用方:

策略 写入速度 文件大小 适用场景
Throughput(.NET 内置) 基准 基准 吞吐优先
Balanced(libdeflate L1) ~2x +14% 性价比最优
Compression(libdeflate L12) 较慢 最小 归档存储

多数业务场景选 Balanced 就够了:用 14% 的体积换 2 倍写入速度,在网络传输和磁盘都不紧张的情况下是划算的。

四、解压引擎:纯托管能追到原生的几成

引擎 1KB 100KB 1MB 10MB 100MB
.NET DeflateStream 1x 1x 1x 1x 1x
LibDeflateDecompressor 0.8x 1.8x 2.1x 2.3x 2.7x
zlib-ng (原生) 2.5x 3.2x 3.5x 3.8x 4.0x

小块数据(<4KB)场景下,Acl.Excel 的解压器略慢于 .NET,因为 .NET 的调用开销更低。在典型 Excel 场景(100KB-10MB),优势稳定在 2x 左右。与原生 zlib-ng 相比仍有 2.4x 差距,但 Acl.Excel 的优势在于零 P/Invoke。

这张表其实回答了一个常被问到的问题:既然原生更快,为什么不直接 P/Invoke?答案是部署成本:零原生依赖意味着不用为每个目标平台准备一份 .so/.dll,容器镜像不用额外装库,AOT 裁剪也不会踩坑。用一部分理论峰值换取"复制即可运行",对绝大多数服务端场景是更优解。

五、压缩器:压缩比与速度怎么取舍

引擎 压缩比 压缩速度 解压速度
.NET DeflateStream Optimal 3.82 基准 基准
LibDeflateCompressor L1 3.15 2.1x 1.9x
LibDeflateCompressor L6 3.91 0.9x 1.9x
LibDeflateCompressor L9 4.05 0.6x 1.9x

L1 级别适合写入吞吐优先的场景,L6 在压缩比接近 .NET Optimal 的同时有更快的解压速度。L9 提供最高压缩比但压缩速度较慢,适合一次压缩多次读取的场景。

有个细节容易被忽略:三个档位的解压速度都是 1.9x,与压缩档位无关。也就是说,如果文件写一次、读很多次,用 L9 多花的压缩时间会被后续每一次读取的收益摊薄。

六、字节扫描器:三种策略的取舍

三扫描器在 100K 行 x 10 列场景下的对比:

扫描器 Mean Allocated RSS 峰值
ByteSheetScanner v1 (Fast) 最快 中等 文件大小相关
ByteSheetScanner2 v2 (SlidingWindow) 最低 恒定 4MB
XmlReaderSheetScanner (Standard)

v2 的滑动窗口策略在超大文件场景下提供恒定的 RSS 峰值内存,是与 MiniExcel 公平对比时使用的策略。

选型逻辑很直接:文件体量可控就用 v1 换速度,文件体量不可控(用户上传、批量导入)就用 v2 保住内存上限。

七、支撑这些数字的测试体系

基准数据只有在"不会悄悄退化"的前提下才有长期价值。Acl.Excel 的测试体系包括:

  • 单元测试:1500+ 用例,覆盖核心组件、压缩引擎、安全防护、边界条件
  • Golden 验证:读取参考文件,验证输出与预期一致
  • 跨库互操作测试:Acl.Excel 写入的文件被其他库读取,反之亦然
  • 性能回归测试:每次代码变更后运行 BenchmarkDotNet 基准测试,性能回退时自动拦截
  • 并发测试:多线程读写场景下的正确性验证

八、性能优化的工程纪律

Acl.Excel 的性能优化遵循严格的工程纪律:

  1. 修改前备份代码
  2. 修改前运行 cdnfull 基准测试(排除 NPOI、ClosedXML、MiniExcel 对比测试)
  3. 修改后运行单元测试
  4. 单元测试通过后运行性能测试
  5. 性能不满足要求时立即回退

这个纪律确保了每次优化都是可验证、可追溯的,避免了"优化后反而变慢"的常见陷阱。

九、关键收获

  • 看分配比看耗时更重要:写入 462 KB 对 165,895 KB 的差距,决定的是服务在高并发下会不会被 GC 拖垮,而不只是单次调用快几毫秒。
  • 读取的领先来自"少做两层":绕过 XmlReader 的字节扫描器,加上绕过 DeflateStream 的 LibDeflate 解压,共同贡献了 5.6 倍到 32.8 倍的差距。
  • 对等条件下的百万行才是硬指标:用较慢的 SlidingWindow 策略去比,仍然拿到 1.88x 读取、2.84x 写入,同时把 RSS 峰值锁在恒定 4MB。
  • 压缩档位按读写比例选:写多读少选 L1(3.15 压缩比、2.1x 速度),写少读多选 L9(4.05 压缩比),三档解压速度同为 1.9x。
  • 零 P/Invoke 是一项主动取舍:相比原生 zlib-ng 留有差距,换来的是无原生依赖、跨平台一致、部署零负担。

下一篇是系列终篇:把本文这些基准数字翻译成可直接照抄的场景选型与调优清单,告诉你什么时候用 SlidingWindow、什么时候切 Balanced、大文件导出该怎么组织代码。

标签(建议):Acl.Excel, .NET, 性能优化, BenchmarkDotNet, Excel 处理


免责声明:本文基于 Acl.Excel 4.0.0(撰写日期前后实测)数据撰写,测试环境与口径见正文;数据可能随版本演进变化,重要决策请自行复测核验。

客观性说明:本文的结论基于以下事实约束,而非自诩无偏——① 本机实测(单一机器、单轮次),运行于 .NET 8/10 Release,使用 BenchmarkDotNet 0.15.8 测量;② 以 Acl.Excel 4.0.0 为基线,对照 MiniExcel 1.45.0、ClosedXML 0.104.0、NPOI 2.7.2 等(竞品版本以正文各表为准),百万行级对等测试数据来自 MiniExcel 官方 benchmark 仓库;③ 未做跨机器复现与长期统计,数据可能随版本演进变化。以上局限已在正文相应位置如实标注,供读者结合完整信息自行判断。

系列导航

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 八大场景与避坑指南(终篇) 实战与避坑指南

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

posted @ 2026-08-25 10:41  风云  阅读(3)  评论(0)    收藏  举报