Skip to content

百万点曲线:Canvas 2D 换成 WebGL 的一次实测 ​

这次在原有 Canvas 2D 方案旁边增加了 WebGL2。页面现在同时保留 Canvas 2D、ECharts 调优、ECharts 朴素和 WebGL 四项;六路合成信号与列聚合逻辑共用,背景网格和十字光标继续用 Canvas 2D。

换完后,两种实现都能流畅显示。本轮百万点实时测试中,两者都维持约 60 次 rAF 回调/秒;全历史回看的 CPU 侧耗时中位数分别为 13.1ms 和 12.3ms。差别很小,也还不能用这组数字给 GPU 的实际绘制速度排名。

从 DOM 到画布,改的是哪一步 ​

写 DOM 时,我们创建一个元素,交给浏览器管理位置、颜色和点击事件。Canvas 也是一个 DOM 元素,但里面的曲线没有各自对应的 DOM 节点,需要自己保存坐标和处理交互。

Canvas 2D 提供现成的画线指令:移到一个点、连到下一个点、设置颜色、描边。WebGL 更底层,这一版把线段两端的坐标装进数组,交给 GPU 上的小段绘图程序,再把每条有宽度的线段画成两个三角形。

两版的共同主线是:

text
原始时序数据 → 可视窗口 → 像素列聚合 → 曲线画面

改变的是最后的绘制过程。数据接收、存储、窗口裁剪和鼠标坐标换算,并没有因为使用 WebGL 就消失。

一百万点,没有全部交给 GPU ​

这个实验先把可视时间范围切成像素列,每列保存最小值、最大值、首值和末值。这台机器的绘图区宽约 1426 个 CSS 像素,去掉左右留白后有 1348 列。

所以 WebGL 接到的是聚合后的线段,而不是直接处理一百万个原始点。六路曲线各有自己的列数据。缩放或切换到全历史时,仍需要从原始数据重建列栅格;同一列内部的采样顺序已经压缩,精确分析还得看原始点。

绘制管线:环形缓冲 → 可视窗口 → 像素列 → 包络 → 增量左移

整条链路五步:数据进环形缓冲 → 按窗口裁 → 切成 1348 个像素列 → 每列压成 4 个数画成包络 → 稳态下每帧只画新增的一列。WebGL 与 Canvas 2D 的区别只在最后一步。

这也解释了为什么不能只凭“百万点”判断应该使用哪种渲染器:存了多少数据、窗口里有多少数据、最后画了多少图形,是不同的量。

这次怎么测 ​

环境是 Chrome 155、8 核、DPR=2,绘图区约 1426×483 CSS 像素,全程保持页面在前台。历史数据来自同一个随机种子,时间平移到每组运行时刻,窗口均为 60 秒。

每组实时流测 4 秒,每 50ms 一批。界面配置为 400 点/秒,但生产器按六路向下整分,每批实际产生 18 点,名义速率为 360 点/秒;本轮每组都收到并绘制了 80 批。

实现历史总点数rAF 回调/秒绘制回调延迟 p95
Canvas 2D 参考10 万6014.1ms
WebGL210 万6010.9ms
ECharts 调优10 万6014.4ms
ECharts 朴素10 万58.636.6ms
Canvas 2D 参考100 万6011.0ms
WebGL2100 万6012.0ms
ECharts 调优100 万6025.6ms

这里的 rAF 是浏览器安排动画回调的频率,不是曲线真正重画的次数。延迟从本地生成批次算到绘制回调;ECharts 使用 rendered 事件,都没有精确测到显示器呈现画面的时刻。

再把窗口拉到三小时全历史,三个实现交替各跑三次:

实现三次 CPU 侧耗时中位数
Canvas 2D 参考18.4 / 13.1 / 10.8ms13.1ms
WebGL214.1 / 12.3 / 12.1ms12.3ms
ECharts 调优258.7 / 231.4 / 238.1ms238.1ms

这组测量覆盖数据处理和绘制调用,但没有等待 WebGL 的 GPU 工作全部结束。12.3ms 不能直接解释成“GPU 用 12.3ms 把画面显示出来了”,也不足以证明它比 Canvas 快了某个固定比例。

Canvas 参考来自替换前的快照,只在参考副本修正了极值绘制中的像素差符号,没有做完整像素级等价比较。线上页面现在提供四种后端(分层 Canvas 2D / WebGL2 试用 / ECharts 调优 / ECharts 朴素),四份实现可以在同一页面上依次复跑;其中「分层 Canvas 2D」即本文的 Canvas 参考。

GPU 为什么没有明显领先 ​

两份代码做的工作不同。旧 Canvas 在量程没变、时间只推进少量列时,会左移已有位图,再补新列;当前 WebGL 为了先跑通,每次更新都会重新组织并上传全部可视列的线段。

百万点实时组里,Canvas 参考记录了 76 次增量绘制、7 次整图绘制,WebGL 则是 82 次整图绘制。比较时,需要同时看硬件和具体实现。

数据规模经过聚合后已经不大,整理坐标、上传数据和发出绘制命令仍有成本。这些成本可能影响 GPU 批量绘制的收益,但本轮没有分别测量,暂时不能确定各部分占了多少。

目前可以确认的是,这台机器上的短测里两种实现都能流畅运行。WebGL 版已上线试用;是否值得继续优化,要看后续实际负载,而不是先给它贴一个“最快”的标签。

显示正确也要单独检查 ​

换 WebGL 前,Canvas 曾经出现首帧能显示、刷新几次后消失的问题:DPR=2 时,把物理像素位图按 CSS 坐标再次放大,连续拷贝后曲线移出了画面。

现在的 WebGL 版不再左移旧位图,而是重新画聚合后的线段。DPR=1、1.25、2 的连续更新、缩放、尺寸变化、全历史回看和 GPU 资源释放检查已通过。它仍是试用版,尚未覆盖上下文丢失恢复、多设备兼容和长时间运行。

打开在线演示,选择“WebGL 曲线(试用)”后启动。项目没有构建步骤,本地用 python3 -m http.server 8765 即可打开;回归入口是 tests/canvas-regression.js。

本轮的原始 JSON和测量说明保留了环境与限制。这是单机、固定顺序的短测,不替代目标设备上的验证。

本站由 GitHub Actions 自动构建部署