2025年3月24日
笔者:ryanho
前段时间看了echarts官网给出来的最佳实践,想测试一下具体的性能差距。
原文:echarts.apache.org/handbook/zh...
- 数据量较大(经验判断 > 1k)、较多交互时,建议选择 Canvas 渲染器。
我注意到这句,所以我们设计测试方案进行对比,来看一下具体结果。
1.设计测试方案
- 测试canvas和svg多实例(100个,1000个)。
- 测试canvas和svg大数据(10000数据量,100000数据量)。
2.创建项目
-
初始化项目
sqlpnpm create vite@latest echarts-performance-test -- --template vanilla cd echarts-performance-test pnpm install pnpm install echarts 启动 pnpm run dev
-
代码部分
index.html
xml<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>ECharts性能测试</title> <style> body { margin: 20px; font-family: Arial; } .controls { margin-bottom: 20px; } button { margin-right: 10px; padding: 8px 12px; cursor: pointer; } #charts-container { display: flex; flex-wrap: wrap; gap: 10px; width: 100%;height: auto; } .chart-item { width: 300px; height: 200px; border: 1px solid #ddd; } #metrics { position: fixed; top: 20px; right: 20px; background: #f5f5f5; padding: 15px; } </style> </head> <body> <div class="controls"> <button onclick="runTest('massInstances', 'canvas')">测试 Canvas 多实例</button> <button onclick="runTest('massInstances', 'svg')">测试 SVG 多实例</button> <button onclick="runTest('bigData', 'canvas')">测试 Canvas 大数据</button> <button onclick="runTest('bigData', 'svg')">测试 SVG 大数据</button> <button onclick="clearCharts()">清理所有图表</button> <label>实例数量: <input type="number" id="instanceCount" value="100"></label> <label>数据量级: <input type="number" id="dataSize" value="10000"></label> </div> <div id="charts-container"></div> <div id="metrics"> <h3>性能指标</h3> <div id="fps">FPS: -</div> <div id="renderTime">渲染时间: -</div> <div id="status">状态: 空闲</div> </div> <script type="module" src="/main.js"></script> </body> </html>
main.js
iniimport * as echarts from 'echarts'; // 全局状态 let charts = []; let fpsLastTime = performance.now(); let fpsFrames = 0; // 性能监控 function startFPSMonitor() { function checkFPS() { const now = performance.now(); const delta = now - fpsLastTime; fpsFrames++; if (delta >= 1000) { const fps = Math.round((fpsFrames * 1000) / delta); document.getElementById('fps').textContent = `FPS: ${fps}`; fpsLastTime = now; fpsFrames = 0; } requestAnimationFrame(checkFPS); } checkFPS(); } // 创建测试图表(添加容器尺寸验证) function createChart(container, renderer, data) { // 确保容器已渲染且具有有效尺寸 if (container.clientWidth === 0 || container.clientHeight === 0) { console.error('容器尺寸异常,请检查 DOM 可见性'); return null; } const chart = echarts.init(container, null, { renderer }); chart.setOption({ dataset: { source: data }, xAxis: { type: 'value' }, yAxis: { type: 'value' }, series: [{ type: 'scatter' }] }); return chart; } // 大数据测试(添加延迟重试机制) function testBigData(renderer, dataSize) { clearCharts(); const data = Array.from({ length: dataSize }, (_, i) => [i, Math.random() * 100]); const container = document.createElement('div'); container.style.width = '100%'; container.style.height = '400px'; document.body.appendChild(container); // 延迟初始化确保容器已渲染 setTimeout(() => { const startTime = performance.now(); const chart = createChart(container, renderer, data); if (!chart) return; const renderTime = performance.now() - startTime; document.getElementById('renderTime').textContent = `渲染时间: ${renderTime.toFixed(2)}ms`; charts.push(chart); }, 50); } // 多实例测试(添加容器预检) function testMassInstances(renderer, instanceCount) { clearCharts(); const container = document.getElementById('charts-container'); // 验证父容器可见性 if (container.offsetParent === null) { console.error('charts-container 容器不可见'); return; } const startTime = performance.now(); for (let i = 0; i < instanceCount; i++) { const div = document.createElement('div'); div.className = 'chart-item'; div.style.width = '300px'; div.style.height = '200px'; // 明确设置子容器尺寸 container.appendChild(div); const chart = createChart(div, renderer, [[i % 10, Math.random() * 100]]); if (!chart) continue; charts.push(chart); } const renderTime = performance.now() - startTime; document.getElementById('renderTime').textContent = `总渲染时间: ${renderTime.toFixed(2)}ms`; } // 清理图表 function clearCharts() { charts.forEach(chart => chart.dispose()); charts = []; const container = document.getElementById('charts-container'); if (container) container.innerHTML = ''; } // 统一测试入口(添加安全校验) function runTest(testType, renderer) { if (!document.getElementById('charts-container')) { console.error('找不到 charts-container 容器'); return; } document.getElementById('status').textContent = '测试运行中...'; const instanceCount = parseInt(document.getElementById('instanceCount').value) || 100; const dataSize = parseInt(document.getElementById('dataSize').value) || 10000; if (testType === 'massInstances') { testMassInstances(renderer, instanceCount); } else if (testType === 'bigData') { testBigData(renderer, dataSize); } setTimeout(() => { document.getElementById('status').textContent = '状态: 空闲'; }, 500); } // 所有 DOM 相关操作在加载完成后执行 document.addEventListener('DOMContentLoaded', () => { // 启动性能监控 startFPSMonitor(); // 挂载函数到 window 对象 window.runTest = runTest; window.clearCharts = clearCharts; // 初始化状态显示 document.getElementById('status').textContent = '状态: 就绪'; // 确保测试容器可见 const container = document.getElementById('charts-container'); if (container) { container.style.display = 'flex'; // 显式设置布局 } });
-
运行后页面如下图:
3.进行测试
在测试的过程中我发现,先测试 Canvas 多实例,再测试SVG多实例,SVG就会更快,反之亦然,所以为了避免误差,每次都重新打开页面进行测试。
3.1多实例测试100个
第一次测试:
canvas 332.30ms
svg 335.80ms
差距很小,canvas更快一点但是差距可以忽略不计。
第二次测试:
canvas 317.80ms
svg 342.40ms
这次可以看到canvas更快一些,后面多次测试也是canvas在100实例的测试中更快一些,实际使用时,人眼观察的差距可以忽略不计。
3.2多实例测试1000个
这次的性能开销更大了会有什么样的结果。
第一次测试:
canvas 2906.50ms
svg 3270.70ms
第二次测试:
canvas 2901.70ms
svg 3272.90ms
两次的测试基本保持同样的结果,在1000实例测试中canvas继续保持了性能的优势。
3.3大数据测试10000数据量
第一次测试:
canvas 50.10ms
svg 48.40ms
第二次测试:
canvas 48.70ms
svg 46.90ms
在大数据量的对比上svg两次的测试结果都更快了一些。
3.4大数据测试100000数据量
第一次测试:
canvas 94.40ms
svg 90.60ms
虽然svg计算出来的时间更快一些,但是svg无法通过gpu加速,所以FPS已经非常的低,实际上网页也处于了不可用状态。
4.结论
SVG 的 GPU 加速能力有限,主要依赖 CPU 进行矢量计算和 DOM 管理,而 Canvas 通过位图操作和 WebGL 能更高效利用 GPU。在需要高性能、大数据量或复杂动画的场景下,Canvas 是更优选择;SVG 则适用于对清晰度和矢量编辑有要求的场景。
在官方建议下,多实例和大数据量下选择canvas渲染器没毛病。