五款JavaScript数据网格横评:80万行滚动性能实测
原文:https://dev.to/astrofly/i-benchmarked-five-javascript-data-grids-with-up-to-800-000-rows-41ck(作者 @astrofly)
数据表格(data grid)是企业级 Web 应用用户每天驻留时间最长的地方。客服后台、物流清单、财务报表,归根结底都是一张表格,而如今用户期望这张表格在高负载下依然保持响应。市面上很多 JS 表格组件在一千行的演示环境里表现出色,但当行数增加几十倍甚至上百倍时会怎样?这才是真正需要把数据表格集成进现代企业级应用的开发者所关心的问题。
披露:我是 DHTMLX 的产品开发者,所以这个问题对我而言并非纯学术探讨。
为了得到一个值得发表的答案,我用同一套测试对五款 JavaScript 表格组件做了基准测试:DHTMLX Grid、AG Grid、Bryntum Grid、DevExtreme DataGrid 和 Kendo UI Grid。基准测试在五种数据规模(从 1 万行到 80 万行)下使用相同的种子数据、行高和滚动负载。这样每款表格产生 40 组配置,五款合计 200 个测量点。
完整的 JavaScript Grid 性能基准测试仓库包含测试套件与配置,可在你自己的机器上复现这些测量。
我测了什么
我聚焦三个指标,覆盖用户通常最容易察觉表格性能问题的主要场景。
构建表格(Building the grid):从初始化表格开始,到视口中出现预期行数为止所用的时间。该测量忽略各表格的 "ready" 信号,因为每个组件对这一时刻的定义并不相同。
跳转到深处(Jump to depth):从程序化跳转到数据集 90% 位置,到视口显示该位置行数据之间的时间。这反映出当用户决定从表格开头跳到接近末尾的位置时,表格能多快跟上。
滚动(Scrolling):单一 FPS 数值不够用,因此我在程序化驱动表格滚动容器移动 50,000 px 的过程中采集了一系列帧耗时。每一帧都被记录下来,以便计算中位数、95 分位和最差帧耗时。FPS 作为补充信息一并报告。
基准测试对五款表格组件使用同一套设置:一个种子数据集、固定 40 px 行高以及 1920×1080 视口。固定 40 px 行高保证各表格之间的滚动负载可比。若没有它,同样的滚动距离在不同表格里覆盖的行数可能不同。每次运行都会校验渲染高度,如果表格不符合 40 px 设置就会报错。
接下来我想稍作旁支,聊一聊每秒帧数(FPS)——这个人人都在公布的指标——并讨论为什么它对 JS 表格组件滚动时的真实表现揭示得少得惊人。
为什么 FPS 数字会误导人
FPS 指标把一整串帧耗时压缩成一个数字。这让它一眼就能比较,但却无法呈现这些帧的分布情况。
举例来说,一款表格大部分帧都很流畅、只是偶有长时间卡顿,另一款表格则每一帧都偏慢。这两款表格组件可能有相近的 FPS 数字,但它们是两种完全不同的行为模式。FPS 展示的是整体产出了多少帧,却对一致性、以及最慢那一帧有多长只字不提。结果就是,FPS 会让偶发卡顿和持续的重度渲染看起来诡异地相似。
还有一个与测试所用 60 Hz 显示器有关的值得注意的因素。在这个刷新率下,在 16.7 ms 内完成的帧 FPS 数值相同。这意味着两款表格可以显示相同的 FPS,但帧耗时却不同。
正因如此,我在滚动测量上采用了不同的方法。不依赖 FPS,而是直接记录帧耗时本身,用它们的分布来评估滚动性能。
为什么 FPS 掩盖了性能差异:一个例子
只需一份基准测试配置,就足以说明为什么帧时间分布比单看 FPS 能提供更多有效信息。
下面的例子使用 10 万行、20 列,并在单元格内渲染真实的 HTML 内容。它用四个与帧相关的指标对比同一滚动场景:帧时间中位数、95 分位数(P95)、观测到的最差帧,以及由此得出的 FPS 值。
- AG Grid:中位帧 16.7 ms,P95 帧 26.19 ms,最差帧 69 ms,FPS 57
- Bryntum:中位帧 16.6 ms,P95 帧 23.1 ms,最差帧 38.5 ms,FPS 60
- DevExtreme:中位帧 18.7 ms,P95 帧 32.65 ms,最差帧 72.8 ms,FPS 51
- DHTMLX:中位帧 16.6 ms,P95 帧 21.23 ms,最差帧 41.6 ms,FPS 60
- Kendo:中位帧 16.9 ms,P95 帧 95.26 ms,最差帧 111.5 ms,FPS 35
在五款表格中,中位数都落在 16.6-18.7 ms 这个狭窄区间内,因此看不出滚动行为上有任何明显差异。P95 参数更能说明问题:DHTMLX 记录到最低值 21.23 ms,其后是 Bryntum 的 23.1 ms、AG Grid 的 26.19 ms、DevExtreme 的 32.65 ms,Kendo 则是 95.26 ms。通俗地说,这些数值意味着有 95% 的已记录帧渲染耗时不超过对应的 P95 值。P95 越低,说明滚动过程中有更大比例的帧保持在较短的帧时间内;数值越高,则意味着慢帧数量更多。
最差帧数值标识的是测量期间观测到的单次最大尖峰。由于该指标只代表一帧,不应把它当作常见滚动性能的特征,但它有助于揭示中位数无法呈现的帧时间峰值。
FPS 结果说明了为什么要参考底层帧时间数据,才能恰当评估滚动性能。DHTMLX 和 Bryntum 都是 60 FPS,AG Grid 是 57 FPS,但它们的 P95 和最差帧数值差异明显得多。这正是查看完整的帧时间指标组能对滚动性能给出更客观视角的原因。
基准测试的重要细节
这项基准测试的设计目标是透明且完全可复现,对五款表格采用同一套方法,以便排除潜在偏差。行生成器由随机种子驱动,因此每个测试场景使用的都是同一份生成数据。基准测试中为各表格添加的所有专属设置都列在项目仓库中,除这些已记录的设置外,没有再做任何其他性能调优。
每项测试在丢弃一次预热后执行五次,测试期间浏览器窗口保持可见。每条结果都会记录本次运行所用的表格版本、机器、浏览器和显示器刷新率。
所有测量均在八核 Ryzen 7 4700U 上进行。
以下是测试所用的具体包与表格版本:
- AG Grid:npm 包
ag-grid-community,实测版本 36.0.2 - Bryntum Grid:npm 包
@bryntum/grid-trial,实测版本 7.3.4 - DevExtreme DataGrid:npm 包
devextreme,实测版本 26.1.3 - DHTMLX Grid:npm 包
@dhx/trial-grid,实测版本 9.3.8 - Kendo UI Grid:npm 包
@progress/kendo-ui,实测版本 2026.2.520
注意:AG Grid 这里实测的是免费的 ag-grid-community 包,其余四款为商业试用版。
本基准测试聚焦渲染、滚动与导航。排序、筛选、流式更新这类操作不在范围内,因为它们有大量可能的实现与使用方式,而基准测试需要选定一条代表性工作流,比较才有意义。基准测试用列数而非横向滚动作为更清晰的性能指标。
不同负载如何影响表格性能
基准测试使用四个负载变量:行数、列数、单元格内容和固定列。每次只改变其中一个变量,其余输入保持不变,这样更容易分离出单个变量对表格性能的影响。
行数
为了观察数据集增长时初始渲染如何变化,下图给出了固定十列、行数从 1 万到 80 万的构建耗时。
- AG Grid:10k 148.6 / 50k 199.4 / 100k 255.8 / 200k 386.1 / 800k 1149.4
- Bryntum:10k 268.6 / 50k 532.3 / 100k 838.8 / 200k 1503.1 / 800k 5330.2
- DevExtreme:10k 180.5 / 50k 182.8 / 100k 178.1 / 200k 179.6 / 800k 186.1
- DHTMLX:10k 55 / 50k 65.5 / 100k 73.8 / 200k 93.3 / 800k 242.8
- Kendo:10k 93.1 / 50k 93.5 / 100k 91.1 / 200k 96.5 / 800k 115.2
构建耗时,单位毫秒,十个模板列。加粗为该列最快。
单纯排名意义不大,不如把数值放到一条符合人们直觉的延迟尺度上看:低于 100 ms 的响应基本感觉是即时的,1 秒左右会明显被察觉,数秒级的延迟则会让应用显得卡顿无响应。对照这条尺度:DHTMLX 在 20 万行以内一直保持在 100 ms 以下,80 万行时为 242.8 ms。Kendo 在五种规模下都稳定在 100 ms 附近,DevExtreme 则维持在 180 ms 左右。AG Grid 在最大数据集之前都低于 1 秒,到 80 万行时构建耗时达到 1149.4 ms。Bryntum 在 20 万行时越过 1 秒线,80 万行时达到 5330.2 ms。
列数
下一个变量是表格配置的列数。下图展示了数据集固定为 10 万行时,列数从 10 增加到 40 过程中 P95 帧时间的变化。整个滚动测试都是垂直方向,因此结果衡量的是更宽表格的渲染开销,而不是横向滚动性能。
- AG Grid:10 列 18.93 / 20 列 26.19 / 40 列 27.1
- Bryntum:10 列 19.4 / 20 列 23.1 / 40 列 29.9
- DevExtreme:10 列 26.34 / 20 列 32.65 / 40 列 30.15
- DHTMLX:10 列 17.4 / 20 列 21.23 / 40 列 21.07
- Kendo:10 列 55.6 / 20 列 95.26 / 40 列 173.69
P95 帧时间,单位毫秒,10 万行。加粗为该列最低。
DHTMLX 在三种列数下都录得最低 P95,且随列数增加变化很小。其余四款被测表格组件从 10 列增至 40 列,P95 增加了 3.67 到 10.5 ms。Kendo 在这一配置下的增幅大得多,从 55.6 ms 升到 173.69 ms,约为 3.1 倍。
单元格模板
单元格内容同样会影响滚动性能,因此这组测试对比了同一数据集的两个版本:一版使用富 HTML 内容,如状态胶囊、星级评分、彩色徽章和进度条;另一版把相同字段渲染为纯文本。每个表格组件都生成相同的标记,保证对比中单元格内容一致。
下表是 10 万行数据下,模板版 P95 减去纯文本版 P95 的结果,单位毫秒。正数表示富内容提高了 P95,负数表示纯文本运行反而略慢。
- AG Grid:10 列 +1.33 / 20 列 +2.59 / 40 列 +3.51
- Bryntum:10 列 +0.7 / 20 列 +2.1 / 40 列 +7.3
- DevExtreme:10 列 −0.16 / 20 列 +6.43 / 40 列 +3.52
- DHTMLX:10 列 +0.2 / 20 列 +3.6 / 40 列 +3.77
- Kendo:10 列 +7.12 / 20 列 +22.16 / 40 列 +49.89
模板单元格 P95 减去纯文本 P95,单位毫秒,数据量为 10 万行。任一方向上约 1 ms 的差异视为测量噪声。
在这几种配置下,五款表格中有四款的富内容单元格只会让 P95 增加几毫秒。只有 Kendo 的增幅随列数增加明显变大,在 40 列时达到 49.89 ms。
更普遍的结论是:富内容单元格并不会自动带来明显的滚动性能惩罚。在这几种配置下,四款表格的实测开销都很有限,而 Kendo 对更复杂的单元格模板表现出更明显的敏感性。
冻结列
接下来测量冻结列对滚动性能的影响。基准测试的做法是:把每个配置固定三列,与同一配置下所有列正常滚动进行对比。
固定三列时的 P95 减去未固定时同一场景的 P95,单位毫秒:
- AG Grid:10 列 @10 万行 +0.67;40 列 @10 万行 −0.31;10 列 @80 万行 +81.7;40 列 @80 万行 +172.56
- Bryntum:10 列 @10 万行 −0.5;40 列 @10 万行 +3.2;10 列 @80 万行 −0.3;40 列 @80 万行 −0.9
- DevExtreme:10 列 @10 万行 +3.36;40 列 @10 万行 +6.91;10 列 @80 万行 +4.08;40 列 @80 万行 +6.52
- DHTMLX:10 列 @10 万行 −0.2;40 列 @10 万行 +0.76;10 列 @80 万行 −0.1;40 列 @80 万行 +1.15
- Kendo:10 列 @10 万行 +1.75;40 列 @10 万行 +4.92;10 列 @80 万行 +1.3;40 列 @80 万行 +7.57
加粗的两个数值是超出测量噪声范围的。
在所测配置下,Bryntum 和 DHTMLX 加上三列冻结不会带来有实际意义的 P95 增长;DevExtreme 和 Kendo 有小幅上升,P95 增加几毫秒。AG Grid 则不同:10 万行时影响可以忽略,但到了 80 万行就变得相当显著——10 列时冻结让 P95 增加 81.7 ms,40 列时增加 172.56 ms。
这带来的实际启示是关于测试而非选型:在 10 万行下测得表现干净的冻结列布局,并不能证明它在 80 万行下同样干净。
内存占用
「这个表格占多少内存」可以指两件不同的事。做页面级预算时,要看的是表格构建完成后堆内存的总量,包括库、数据集和表格本身;而要横向比较各表格实例本身,基准测试衡量的是库和数据集加载之后,表格额外占用了多少堆内存。
因此基准测试同时给出这两个数字。每次测量前都会强制触发一次垃圾回收,把不再使用的对象清理掉。
- AG Grid:页面总占用 320.12 MB;仅表格 178.78 MB
- Bryntum:页面总占用 905.62 MB;仅表格 761.66 MB
- DevExtreme:页面总占用 164.03 MB;仅表格 13.85 MB
- DHTMLX:页面总占用 170.97 MB;仅表格 30.58 MB
- Kendo:页面总占用 162.95 MB;仅表格 15.14 MB
80 万行、40 个模板列。加粗为各列中的最低值。
📌 800 000 rows, 40 template columns. Bold marks the lowest in each column(图,点击查看)
这两个测量回答的是不同问题,所以页面总占用更低的表格,仅表格内存不一定更低。这也是两个结果都要报出的原因。在 80 万行、40 个模板列下,页面总占用从 Kendo 的 162.95 MB 到 Bryntum 的 905.62 MB 不等,仅表格内存则从 DevExtreme 的 13.85 MB 到 Bryntum 的 761.66 MB 不等。DHTMLX 的页面总占用为 170.97 MB、仅表格为 30.58 MB,两项都保持在相对较低的水平。
页面总占用与仅表格之间的差值,代表表格构建之前就已存在的内存,包括已加载的库和数据集。观察这个差值随列数增加的变化还能得到一个额外线索:从 10 列增加到 40 列时,五款表格的差值在 1 万行下都增长约 1.14–1.15 MB,在 10 万行下增长约 11.44–11.46 MB。由于整个基准测试使用的是同一份数据集,这种同步增长说明这部分新增内存大部分来自共享数据,而不是来自表格自身的实现。
剩余的差异可以用来估算这部分内存中与库相关的部分。在所测表格中,这个估算值大约在 2.72 MB 到 12.51 MB 之间。这些数字代表已加载库推断出的内存贡献,而不是它们在磁盘上的包体积。
还有一组数据便于按行数估算页面体积预算。每多一千行,页面总体积就增加这么多(每 1000 行增量,十个模板列):
- AG Grid:279 KB
- Bryntum:307 KB
- DevExtreme:70 KB
- DHTMLX:97 KB
- Kendo:72 KB
每 1000 行的页面总体积,十个模板列,加粗为最低值。在 40 列下,同样的增幅区间为 187 KB 到 1147 KB。
在十个模板列下,DevExtreme、Kendo 和 DHTMLX 每 1000 行增加 70 到 97 KB,而 AG Grid 和 Bryntum 的增量明显更大。同样的增长规律在 40 列时更为显著,实测每 1000 行的增幅区间为 187 KB 到 1147 KB。
负载测试结果更完整地反映了五款表格在不同条件下的表现。下一节会把这些发现按每款表格汇总起来。
各表格组件的性能画像
对测试结果的分析表明,随着负载变化,五款表格组件的反应各不相同。下面的列表汇总了每款组件在基准测试中呈现的性能规律。
- DHTMLX Grid:在整个基准矩阵中表现格外稳定,全部 40 个场景的中位帧时间维持在 16.6-16.7 ms,P95 在 17.1-23.05 ms 之间,没有任何一帧超过 45 ms。随着负载增长,构建时间从 55 ms 升至 242.8 ms,跳转时间保持在 32.8-49.1 ms。列数变化、单元格内容变化以及固定列都不会造成明显的性能劣化。
- AG Grid:在 20 万行以内性能相对稳定,到了 80 万行的场景帧时间明显升高。80 万行的各项配置下中位帧时间达到 31.9 ms。压力最大的场景(40 列 + 3 个固定列)中位帧时间 95.5 ms、P95 达 228 ms。在该配置下,AG Grid 滚完固定的 50,000 px 距离需要 30.8 秒,而 DHTMLX 只要 3.7 秒。数据量较小时固定列几乎测不出额外开销,但到 80 万行时影响大幅上升:实测 P95 分别增加 81.7 ms(10 列)和 172.56 ms(40 列)。
- Bryntum Grid:主要开销出现在大数据集的首次渲染阶段。80 万行 + 10 列时构建时间达到 5,330.2 ms,40 列时为 12,292.1 ms,仅表格组件本身的堆占用就达到 761.66 MB,是本次基准中的最高值。表格就绪之后帧时间趋于稳定,在各项被测配置中 P95 保持在 18.4-33.32 ms。
- DevExtreme DataGrid:构建时间几乎不随行数变化,从 1 万行的 180.5 ms 到 80 万行的 186.1 ms——数据集放大了 80 倍,构建时间却基本没动。但细看滚动环节,情况就不一样了:矩阵中 P95 在 25.9-40.29 ms 之间,10 万行时滚完固定的 50,000 px 需要 3,279.9 ms。各被测数据量下,固定列会给 P95 增加 3.36-6.91 ms。
- Kendo UI Grid:在被测场景中,P95 对列数格外敏感。100,000 行时,P95 从 10 列的 55.6 ms 升到 20 列的 95.26 ms、40 列的 173.69 ms。相比之下,40 列的 P95 从 1 万行的 168 ms 变到 80 万行的 196.33 ms,行数带来的变化反而更小。另外两处对性能敏感的地方是富单元格内容(40 列时给 P95 增加最多 49.89 ms)和导航跳转(Jump Time)——80 万行 + 40 列 + 3 个固定列时达到 762.9 ms。
综合来看,这些结果说明了为什么只看单一的数据量或列配置,无法完整刻画表格组件的性能。不同的负载维度会在矩阵的不同部分造成最大幅度的变化,因此针对实际应用,把行数、列数、单元格复杂度和固定列的组合一起测一遍至关重要。
自己跑一遍基准测试
在项目仓库中执行以下命令即可完整复现本次基准测试:
npm ci npm run vendor npx playwright install chromium npm run bench -- --set=article --sizes=10000,50000,100000,200000,800000 --runs=5 --warmup=1 --order=random --cleanup npm run report
完整的基准测试套件(包括测试方法、配置与可复现的结果)都在 GitHub 仓库 中。
基准测试运行时会显示浏览器窗口,因此整个测试过程中要保持屏幕常亮,并关闭任何可能中断测试的屏保或休眠行为。视机器性能不同,跑完全程可能需要数小时。如果中途停止,已完成配置的结果会被保留,之后仍可纳入报告。
总结
本次基准测试说明了为什么单看一个 FPS 数值或单一数据量不足以描述表格组件的性能。中位数、P95 和最差帧的结果能暴露平均 FPS 掩盖掉的差异;而构建时间、Jump Time 与内存占用,则会随行数、列数、单元格内容和固定列的变化呈现出各不相同的响应方式。
DHTMLX 的性能画像格外稳定,全部 40 个配置的中位帧时间为 16.6-16.7 ms、P95 为 17.1-23.05 ms。其他几款表格对特定负载变量的敏感度更明显,而最大幅度的变化出现在基准测试的不同部分。
上面的数字是有参考价值的,但真正能为你的应用定下结论的那次测试,得是在你自己的硬件上、用你自己的数据和列组合跑出来的那一次。
原文:https://dev.to/astrofly/i-benchmarked-five-javascript-data-grids-with-up-to-800-000-rows-41ck(作者 @astrofly)