Skip to content

Benchmark测试性能

Benchmark.js 是一个强大的 JavaScript 基准测试库,用于精确测量代码性能。本文将详细介绍其使用方法,包含完整示例代码。

安装方法

使用 npm 安装

bash
npm install benchmark --save

浏览器直接引入

html
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/benchmark@2.1.4/benchmark.min.js"></script>

基础用法

javascript
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite;

// 添加测试用例
suite
  .add('RegExp#test', () => {
    /o/.test('Hello World!');
  })
  .add('String#indexOf', () => {
    'Hello World!'.indexOf('o') > -1;
  })
  .add('String#includes', () => {
    'Hello World!'.includes('o');
  })
  // 添加事件监听器
  .on('cycle', event => {
    console.log(String(event.target));
  })
  .on('complete', function() {
    console.log('最快的方法是: ' + this.filter('fastest').map('name'));
  })
  // 运行测试
  .run({ 'async': true });

关键概念

1. Suite(测试套件)

测试用例的容器,用于组织和管理多个基准测试

2. Test(测试用例)

单个需要测试的代码片段

3. 重要事件:

  • cycle: 每个测试完成时触发
  • complete: 所有测试完成后触发
  • start: 测试套件开始时触发
  • error: 测试出错时触发

高级配置

测试用例配置选项

javascript
suite.add('Custom test', {
  fn: function() { /* 测试代码 */ },
  minSamples: 50,     // 最小样本数
  maxTime: 5,          // 最大运行时间(秒)
  delay: 0.1,          // 测试之间的延迟
  initCount: 5,        // 初始预热次数
  name: '自定义测试',   // 测试名称
  onStart: function() { /* 测试开始时 */ },
  onCycle: function() { /* 每次循环后 */ },
  onComplete: function() { /* 测试完成后 */ },
  onError: function() { /* 出错时 */ }
});

异步测试

javascript
suite.add('Async test', {
  defer: true,
  fn: function(deferred) {
    setTimeout(function() {
      // 异步操作
      deferred.resolve();
    }, 50);
  }
});

测试结果解析

测试结果包含的关键指标:

text
String#indexOf x 62,405,689 ops/sec ±1.21% (92 runs sampled)
  • ops/sec: 每秒操作次数(越高越好)
  • ±1.21%: 误差范围(越小越稳定)
  • (92 runs sampled): 采样次数

最佳实践

  1. 避免死码消除:确保测试代码有实际输出
javascript
// 错误方式(会被优化掉)
let sum = 0;
for (let i = 0; i < array.length; i++) {}

// 正确方式
let sum = 0;
for (let i = 0; i < array.length; i++) {
  sum += array[i];
}
return sum;
  1. 使用Setup/Teardown:准备测试环境
javascript
suite.add('Array#forEach', {
  setup: function() {
    this.array = Array(1000).fill().map((_, i) => i);
  },
  fn: function() {
    let sum = 0;
    this.array.forEach(n => sum += n);
    return sum;
  }
});
  1. 控制测试环境
javascript
// 在Node.js中禁用优化
function noop() {}
const testFn = Benchmark.prototype._createTest(test);
testFn();
noop(testFn);

完整示例

html
<!DOCTYPE html>
<html>
<head>
  <title>Benchmark.js 示例</title>
  <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/benchmark@2.1.4/benchmark.min.js"></script>
  <style>
    body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
    .results { margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 5px; }
    .test-case { margin-bottom: 15px; padding: 10px; border-left: 4px solid #4285f4; }
    .fastest { border-left-color: #34a853; background-color: #e6f4ea; }
    .chart { height: 300px; margin: 20px 0; display: flex; align-items: flex-end; gap: 10px; }
    .bar { flex: 1; background: #4285f4; position: relative; }
    .bar-label { position: absolute; bottom: -25px; width: 100%; text-align: center; }
    .bar-value { position: absolute; top: -25px; width: 100%; text-align: center; font-weight: bold; }
    button { padding: 10px 20px; background: #4285f4; color: white; border: none; border-radius: 4px; cursor: pointer; }
    button:hover { background: #3367d6; }
  </style>
</head>
<body>
  <h1>Benchmark.js 性能测试</h1>
  <button id="runTests">运行测试</button>
  <div class="results" id="results"></div>
  
  <script>
    document.getElementById('runTests').addEventListener('click', runBenchmarks);
    
    function runBenchmarks() {
      const resultsElement = document.getElementById('results');
      resultsElement.innerHTML = '<p>运行测试中...</p>';
      
      const suite = new Benchmark.Suite;
      const testData = Array(10000).fill().map((_, i) => i);
      let results = [];
      
      // 添加测试用例
      suite
        .add('for循环', function() {
          let sum = 0;
          for (let i = 0; i < testData.length; i++) {
            sum += testData[i];
          }
          return sum;
        })
        .add('for...of循环', function() {
          let sum = 0;
          for (const value of testData) {
            sum += value;
          }
          return sum;
        })
        .add('Array#reduce', function() {
          return testData.reduce((sum, val) => sum + val, 0);
        })
        .add('Array#forEach', function() {
          let sum = 0;
          testData.forEach(val => sum += val);
          return sum;
        })
        .add('while循环', function() {
          let sum = 0;
          let i = testData.length;
          while (i--) {
            sum += testData[i];
          }
          return sum;
        })
        .on('cycle', event => {
          const result = {
            name: event.target.name,
            hz: event.target.hz,
            stats: event.target.stats
          };
          results.push(result);
          resultsElement.innerHTML += `<div class="test-case">${String(event.target)}</div>`;
        })
        .on('complete', function() {
          const fastest = this.filter('fastest')[0].name;
          resultsElement.innerHTML += `<div class="test-case fastest">最快的方法是: ${fastest}</div>`;
          
          // 渲染柱状图
          renderBarChart(results, fastest);
        })
        .run({ 'async': true });
    }
    
    function renderBarChart(results, fastest) {
      const maxOps = Math.max(...results.map(r => r.hz));
      const chartElement = document.createElement('div');
      chartElement.className = 'chart';
      
      results.forEach(result => {
        const height = (result.hz / maxOps) * 100;
        const isFastest = result.name === fastest;
        
        const bar = document.createElement('div');
        bar.className = 'bar';
        bar.style.height = `${height}%`;
        bar.style.background = isFastest ? '#34a853' : '#4285f4';
        
        const value = document.createElement('div');
        value.className = 'bar-value';
        value.textContent = (result.hz / 1000000).toFixed(2) + 'M';
        
        const label = document.createElement('div');
        label.className = 'bar-label';
        label.textContent = result.name;
        
        bar.appendChild(value);
        bar.appendChild(label);
        chartElement.appendChild(bar);
      });
      
      document.getElementById('results').appendChild(chartElement);
    }
  </script>
</body>
</html>

常见问题解决

  1. 测试结果波动大

    • 增加 minSamplesmaxTime
    • 关闭后台应用程序
    • 禁用浏览器扩展
  2. 测试函数被优化掉

    • 确保有实际输出
    • 使用 this 上下文存储结果
    javascript
    suite.add('Test', function() {
      this.result = /* 计算结果 */;
    });
  3. 内存问题

    • setup 中初始化数据
    • teardown 中清理资源
    • 避免在测试中创建大型对象

Benchmark.js 提供了专业级的性能测试能力,通过合理配置和遵循最佳实践,您可以获得准确的性能数据来优化代码。