Stats.js 深度原理揭秘

为什么 mrdoob/stats.js 能精准监控性能?
核心在于浏览器提供的 High Resolution Time APIV8 Memory API

如何集成 (Usage)

Quick Start
只需简单三步即可将监控面板添加到你的 WebGL/Canvas 项目中:
// 1. 实例化 & 配置
var stats = new Stats();
stats.showPanel( 0 ); // 0: fps, 1: ms, 2: mb

// 2. 添加到 DOM
document.body.appendChild( stats.dom );

// 3. 注入渲染循环
function animate() {
    stats.begin();
    
    // ... 你的渲染代码 (renderer.render) ...
    
    stats.end();
    requestAnimationFrame( animate );
}
requestAnimationFrame( animate );

核心原理拆解

MS: 毫秒级精准计时 performance.now()

原理:High Resolution Time API

为什么准? Stats.js 不使用 Date.now(),而是使用 performance.now()

  • 微秒精度: Date.now() 是整数毫秒,而 performance.now() 返回浮点数(如 16.695ms),精确到微秒级。
  • 单调递增: 它基于页面加载时间,不受系统时间调整(如用户修改操作系统时间)的影响。
// 1. stats.begin() 内部:
// 获取高精度时间戳 (例如: 2500.1200000047684)
var beginTime = ( performance || Date ).now();

// ... 执行你的渲染代码 ...

// 2. stats.end() 内部:
var time = ( performance || Date ).now();
// 精确计算差值,甚至能捕捉到 0.1ms 的波动
var ms = time - beginTime;

MB: 内存数据来源 Chrome Only

原理:Chrome V8 非标准扩展 API

数据从哪来? 这是一个 Chrome/Chromium 特有的私有 API performance.memory。Firefox 和 Safari 出于安全隐私考虑(防止指纹追踪)默认不提供此数据。

  • usedJSHeapSize: 当前 JS 对象实际占用的堆内存。
  • jsHeapSizeLimit: 当前上下文允许的最大堆内存。
// 浏览器控制台输入 performance.memory 查看:
{
    jsHeapSizeLimit: 4294705152, // ~4GB 上限
    totalJSHeapSize: 23545125,   // 已申请
    usedJSHeapSize: 18454321     // 实际使用 <--- Stats.js 监控此值
}

FPS: 帧率统计算法

requestAnimationFrame

简单地计算 `1 / delta` 会导致数值剧烈跳动。Stats.js 采用"一秒一结"的策略:累加这一秒内执行了多少次 `end()`,然后刷新显示。

frames++; 
if (time > prevTime + 1000) {
    // 这一秒内渲染了多少帧?
    fps = Math.round( ( frames * 1000 ) / ( time - prevTime ) );
    prevTime = time;
    frames = 0;
}

原理复刻演示

实时监控 Demo

完全基于左侧原理手写的 MiniStats

0 FPS 0 MS
Canvas 动画场景
> stats.begin(); // 记录高精度时间
> renderScene(); // 渲染粒子
> heavyTask(); // 空闲
> stats.end(); // performance.now() - begin

模拟高负载的原理:阻塞主线程

JavaScript 运行在浏览器的主线程上。如果我们使用 while 循环强行占用 CPU,浏览器就无法去处理绘制下一帧的任务,从而产生“卡顿”。

// 阻塞主线程逻辑
function heavyTask() {
const start = performance.now();
// 死循环强行占用 40ms
while (performance.now() - start < 40) {
// 期间 CPU 忙于计算,无法渲染
Math.sqrt(Math.random());
}
}
  • 对 MS 的影响: stats.end() 测得的时间 = 渲染耗时 + 40ms 阻塞时间。MS 面板数值直接飙升。
  • 对 FPS 的影响: 由于每一帧至少耗时 40ms,1秒内最多只能画出 25 帧 (1000ms / 40ms = 25)。FPS 从 60 骤降至 25。