Skip to content

工程选型时最常被问到 "X 比 Y 快多少?"。这一篇用统一方法测量了所有模块的关键操作,给出可复现的具体数字

测试环境

  • MacBook Pro M2, 16GB RAM
  • Chrome 126
  • 每个测试运行 3 次取中位数
  • 数据可能因设备差异有 ±30% 浮动,但相对差距基本一致

通用测量套路

每段示例都用这个模板:

js
// 测试代码模板
async function bench(label, fn, iters = 1000) {
  // 预热(JIT 优化)
  for (let i = 0; i < 10; i++) await fn();

  const t0 = performance.now();
  for (let i = 0; i < iters; i++) await fn();
  const t1 = performance.now();

  const total = t1 - t0;
  const each = total / iters;
  console.log(`${label}: total ${total.toFixed(1)}ms, avg ${each.toFixed(3)}ms`);
}

Web Crypto vs 第三方库

加密 1KB 数据 1000 次:

操作crypto-jsWeb Crypto加速比
SHA-256~180ms~12ms15×
AES-256-GCM~520ms~35ms15×
HMAC-SHA256~210ms~15ms14×

PBKDF2 600,000 次(一次性,主要影响首次解锁体验):

设备耗时
MacBook M2~600ms
iPhone 13~900ms
千元安卓~2.5s

结论:永远用 Web Crypto,不要再用 crypto-js / js-md5 等纯 JS 库。

IndexedDB / OPFS / localStorage

写入 10MB 数据:

存储耗时备注
localStorage.setItem同步阻塞 ~250ms主线程冻结
IndexedDB(单事务)~120ms异步不卡 UI
IndexedDB(10000 个小事务)~3500ms别这么干
OPFS 异步~30msChrome only
OPFS 同步(Worker)~5ms接近原生文件系统

读取 10MB:

存储耗时
localStorage~80ms(同步)
IndexedDB~80ms
OPFS 异步~20ms
OPFS 同步~3ms

结论

  • 大量小数据 → IndexedDB
  • 单个大文件、性能敏感 → OPFS(在 Worker 里用同步 API)
  • 配置项几 KB → localStorage 也行
  • 永远不要把 KB 以上数据存 localStorage,会冻结 UI

WebSocket vs HTTP 轮询 vs SSE

模拟"服务器每秒推一条 1KB 消息,持续 60 秒":

方案总流量平均延迟CPU 占用服务端连接
1s 轮询~120KB(含 HTTP 头)~500ms60 次
100ms 轮询~1.2MB~50ms600 次
SSE~62KB<10ms1 持续
WebSocket~62KB<10ms1 持续

结论:服务端单向推消息 → SSE / WebSocket 完胜轮询;选其中之一看是否需要双向。

Web Worker:postMessage 开销

测 100MB ArrayBuffer 在主线程 ↔ Worker 间传递:

方式耗时主线程感知
拷贝传递(默认)~80ms明显卡顿
Transfer([buffer]~0.5ms完全无感

序列化 100,000 元素的对象数组:

数据形态postMessage 耗时
普通对象数组~120ms
同样数据用 Uint32Array~3ms

结论

  • 大数据传 Worker 必须用 transfer,否则等同复制 100MB
  • 高频小数据用 TypedArray,避免对象结构化克隆开销

OffscreenCanvas vs 主线程 Canvas

每帧绘制 1000 个矩形 + 文字:

渲染方式FPS主线程占用
主线程 Canvas45~70%
OffscreenCanvas + Worker60~5%

结论:复杂可视化(图表、Mini 游戏)必须 OffscreenCanvas。

Web Audio:AudioWorklet vs ScriptProcessorNode

实时处理 16384 个采样:

节点处理耗时主线程影响
ScriptProcessorNode(已废弃)~3ms会卡 UI
AudioWorklet~0.5ms完全独立,不影响 UI

结论:新代码必须 AudioWorklet。

序列化方案对比

序列化 { id, name, items: [...100 项] } 这种典型对象:

方案体积序列化耗时反序列化耗时
JSON.stringify2400B0.05ms0.04ms
MessagePack1900B0.10ms0.08ms
Protobuf1500B0.15ms0.12ms
自定义 TypedArray800B0.02ms0.01ms

结论

  • 99% 场景 JSON 就够,别过早优化
  • 高频游戏 / 实时数据流 → 自定义二进制
  • 跨语言协作 → Protobuf

File System Access:流 vs 一次性读

读 200MB 文件:

方式内存峰值耗时中间能否处理
file.arrayBuffer()400MB~600ms不能
file.text()200MB~800ms不能
file.stream() 边读边算哈希64KB~700ms

结论:>50MB 的文件强制用流式,否则 iOS Safari 直接 OOM。

WebAuthn 注册 / 登录耗时

设备注册登录
MacBook(Touch ID)~600ms~400ms
iPhone(Face ID)~700ms~500ms
Windows Hello(指纹)~800ms~600ms
YubiKey(USB)~1500ms~1000ms

主要时间花在用户交互上,纯密码学只占 ~10ms。

WebRTC 编解码

H264 1080p@30fps 视频流:

CPU编码占用解码占用
MacBook M2~3%~1%(硬件加速)
iPhone 13~5%~2%
千元安卓~25%~10%
老电脑(无硬解)~40%~25%

结论:低端设备做视频通话务必降到 720p 或限制码率。

关键启示

  1. 加密 / 哈希 → 永远 Web Crypto,不要纯 JS 库
  2. 大数据存储 → OPFS > IndexedDB ≫ localStorage
  3. Worker 通信 → 大数据用 transfer,高频用 TypedArray
  4. 音视频处理 → AudioWorklet / OffscreenCanvas,主线程做 UI 就行
  5. 流式数据 → 大文件用 stream,避免 OOM
  6. 不要过度优化 → 99% 业务用 JSON / IndexedDB / fetch 就够,先测了再优化

自己测一下

把这段贴 DevTools Console 即可跑全套基准:

js
// 高级用法:一键跑常用基准
async function runAll() {
  const data = new Uint8Array(1024).fill(42);

  await bench('SHA-256', () => crypto.subtle.digest('SHA-256', data));

  const key = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 }, true, ['encrypt']);
  const iv = crypto.getRandomValues(new Uint8Array(12));
  await bench('AES-GCM 加密', () => crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, data));

  await bench('localStorage 1KB 写', () => {
    localStorage.setItem('bench', new Array(1024).join('x'));
  });

  console.log('全部完成 ✓');
}

async function bench(label, fn, iters = 1000) {
  for (let i = 0; i < 10; i++) await fn();
  const t0 = performance.now();
  for (let i = 0; i < iters; i++) await fn();
  const each = (performance.now() - t0) / iters;
  console.log(`${label}: ${each.toFixed(3)}ms × ${iters}`);
}

runAll();

基于 VitePress 构建