聊一聊Echarts百万级数据时的渲染优化

16,572 阅读5分钟

问题现象

本人在字节开发一款性能采集工具,客户端技术栈使用Electron+React+Echarts实现,下文以G简称该工具。 G可同时采集多个指标,并通过Echarts进行实时绘制,但在时间稍长的情况下,对图表进行的各类操作会变得卡顿无比,稍一拖拽,CPU便暴涨得厉害。

原因分析

G支持数十种性能图表,用户可根据自己需要客制化展示项。数十张普通图表,并且加上FrameTime(帧渲染时间,高刷新率手机上每秒有百来个数据),极端情况下,每秒需要绘制近两百个点。而G的使用场景经常需要连测几个小时,那么界面中需要渲染的点数就是惊人的 2x3600x200 =1440000个!!

而使用Echarts图表自带的缩放功能时,这百万个点会全部遍历并更新状态。然而想维持流畅的动画,至少每秒要有30帧,要在33.3ms间遍历百万个点,压力颇大。

如此大的数据量,对渲染的压力非常大,所以卡顿也在情理之中?(当然不是,勇敢牛牛,不怕困难,这就优化)。

卡顿如何优化?

有两个基本法。

  1. 增强计算能力
  2. 减少计算量

放在G图表渲染上来说,就是尽量减少数据点的渲染并提高渲染性能。

问题解决

首先来提高渲染性能。

Web Worker

Echarts本身是一个基于 JavaScript 的开源可视化图表库。运行在浏览器端的js只能进行单线程执行。当计算量较大,使得绘制每帧的时间超过16.6ms时,就会出现人眼可见的卡顿现象。若要加强此处性能,Web Worker当仁不让。

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

此处便开始实践,先尝试Demo,将整个Echarts扔到Web Worker,确实流畅很多。

但是!!!

失去了所有的交互能力!!

没办法了,只能开始看源码,翻遍了整个zrender代码(2D的向量绘制库Zrender,其允许基于Canvas进行绘制同时获得图形元素的事件能力)只找到了判断当前环境为worker的代码、但还没有找到和worker通讯相关的代码。

如果这样使用的话,就需要将整个Echarts里交互的再实现一部分,成本太大了,并在Echarts开发人员的知乎得到了这样的解答

其实Echarts有做一些比较激进的带交互的尝试的(worker 里负责生成渲染指令,然后渲染指令传回主线程绘制,因为当时 OffscreenCanvas 还不是很能用),但是后来觉得引出的问题要大于带来的收益,当时主要的考虑是就算在 worker 线程里,渲染时间大于 16ms 所带来的交互延迟的感觉还是一样的,最大的优势是不会阻塞 UI 线程,但是这一块我们主要方向是通过渐进渲染来改善 UI 线程的阻塞问题,所以 worker 的渲染就先搁置了

所以通过Worker线程来优化并不是一条好路。

那只能将目光投向减少计算量了,这个方向的实现相对轻松很多。

全量渲染改增量

接着我想到的是将全量渲染改为增量渲染。

虽然这并不能解决拖动时的卡顿(因为仍旧需要遍历所有点),但能将每秒渲染的全量数据从百万改成几百个的增量数据,至少能给CPU放个假。

说干就干,当时也马上在Echarts的官网里找到了对应的api,appendData(这个接口在Echarts5中已经找不到了),试了下,发现效果不显,CPU占用没降,卡顿依旧明显。

研究了下Echarts的实现,其在填入数据时做了diff,而G每次填入的都是在原有数据基础上新增一秒数据,所以在diff算法的加持下,早就是增量渲染了。

懒加载

走投无路之下,便想砍掉几张图表。

G支持的性能数据颇多,同时需要绘制很多张图表。

但实际上电脑屏幕并不足够大,展示给用户的可视范围并不大,我们可以只实时渲染用户所见的部分,其余部分通过监听滚轮,鼠标,键盘的操作,当其可视时,再进行渲染。

可以看到一帧的时间已经达到了100ms+,其中占据较多时间的是Echart.setOption这个函数。实际上,Echarts在渲染单张图表时表现还是不错的,一般能做到20ms内,但因为我们图表共有数十张,10*10=100。改成滚动时加载之后帧渲染便好了极多。

然而,更大的挑战还在后头。那就是面对海量数据时的渲染优化。

降采样

实际上,我们可以注意到,G上的一个折线图,一般占用的像素宽度大概只有700到1500,就算是百万个点,能展示出来的也不过寥寥,这里便大有文章可做。

ECharts 有 提供 sampling 功能 ,其中有max,min,avg以及lttb等。使用G进行性能测试的同学很多时候,都是需要观测离群值的,所以max,min以及avg势必会丢弃很多细节,没法使用。

那么lttb呢?

相当优秀。

降采样前

使用lttb降采样后

可以看出,使用了lttb算法之后,细节和趋势都保留的非常好,而且每帧渲染耗时更是从之前的成百上千降至50ms内,相当给力。

延展阅读:LTTB 降采样算法初试 - n4mine's blog