淺談基準測試

jopen 8年前發布 | 34K 次閱讀 性能測試 單元測試 軟件測試

在一個應用中,應前端要求需要過濾后端接口響應JSON數據中的 null 字段,過濾操作會有性能影響,那么如何決定是否增加這個功能呢?

首先需要確定衡量指標。通常時間(time)和空間(memory)是兩個衡量程序性能狀況額指標,在這個例子中空間并不是制約因素,因而只考慮時間指標。

實現

接著我們需要一個程序實現。這個實現簡單的遞歸過濾 Object 中值為 null 的字段,

/**

  • 不過濾數組元素為null的情況,如
  • [null, 'foo', null]過濾后仍然為[null, 'foo', null] */ function prune(data) { if (_.isArray(data)) {
     _.each(data, prune)
    
    } else if (_.isObject(data)) {
     _.each(data, function(value, key) {
         if (_.isObject(value)) {
             prune(value)
         } else if (value === null) {
             delete data[key]
         }
     })
    
    } return data }</pre>

    單元測試見附錄。

    影響因素

    然后根據程序實現判斷性能的影響因素

    什么因素會影響時間指標呢?JSON數據的大小(size)?JSON數據的字段數?JSON數據的層次結構?

    時間指標受JSON數據的字段(包括遞歸字段)影響,因為在 prune 的實現中,遍歷 Object 和 Array 的時間決定了程序執行時間。

    benchmark

    最后根據影響因素選擇測試數據,進行基準測試并得出結論

    借助 benchmark.js ,以 noop 為參照組進行基準測試

    有兩組測試數據,真實線上接口獲取的 realSamples 和隨機生成的模擬數據 fakeSamples 。

    var fs = require('fs')
    var Benchmark = require('benchmark')
    var suite = new Benchmark.Suite()
    var getSample = require('./sample').getSample
    var getSampleSize = require('./sample').getSampleSize
    var prune = require('../src/utility').prune
    var noop = function(){}

var filenames = fs.readdirSync(dirname + '/samples') var realSamples = filenames .map(function(filename) { return JSON.parse( fs.readFileSync(dirname + /samples/${filename}, 'utf8') ) }) var realSizes = realSamples.map(getSampleSize)

var fakeSizes = [10, 100, 1000, 10000] var fakeSamples = fakeSizes.map(function(size) { return getSample(size) })

// add tests realSamples.forEach(function(sample, index) { var filename = filenames[index] suite .add(prune#real:${filename}:${getSampleSize(sample)}, function() { prune(sample) }) })

fakeSamples.forEach(function(sample, index) { var size = fakeSizes[index] suite .add(prune#fake:${size}:${getSampleSize(sample)}, function() { prune(sample) }) }) suite .add('noop', function() { noop(realSamples[0]) }) // add listeners suite .on('cycle', function(event) { console.log(String(event.target)) }) .on('complete', function() { var totalSize = realSizes.reduce(function(sum, size) { return sum + size }, 0) var averageSize = Math.floor(totalSize / realSizes.length) console.log(real samples total size ${totalSize}, average size ${averageSize}) console.log('Fastest is ' + this.filter('fastest').map('name')) }) // run .run()</pre>

這里還實現了 getSampleSize 方法(見附錄),用于統計JSON數據的字段總量。以此來粗略估計線上真實接口返回數據的平均字段數量。

運行結果 [1]

prune#real:adverts.json:47 x 179,918 ops/sec ±2.10% (79 runs sampled)
prune#real:areas.json:5126 x 1,333 ops/sec ±2.47% (74 runs sampled)
prune#real:citys.json:6417 x 1,363 ops/sec ±1.07% (90 runs sampled)
prune#real:count.json:1037 x 6,043 ops/sec ±1.75% (89 runs sampled)
prune#real:menus.json:55 x 47,010 ops/sec ±1.34% (86 runs sampled)
prune#real:pois.json:3136 x 2,316 ops/sec ±3.16% (84 runs sampled)
prune#real:subway.json:1999 x 3,043 ops/sec ±1.85% (89 runs sampled)
prune#fake:10:10 x 286,702 ops/sec ±1.64% (88 runs sampled)
prune#fake:100:100 x 63,893 ops/sec ±1.56% (89 runs sampled)
prune#fake:1000:985 x 8,173 ops/sec ±1.63% (86 runs sampled)
prune#fake:10000:9995 x 997 ops/sec ±1.80% (87 runs sampled)
noop x 80,713,438 ops/sec ±1.85% (87 runs sampled)
real samples total size 17817, average size 2545
Fastest is noop

結論:平均字段總量為 2545 ,向上取證以 10000 量級計算,使用 prune 處理數據大約需要1ms,并不影響整個應用的性能。

小結

上面已經用黑體標記了重點,這里再做一次小結

  1. 確定衡量指標
  2. 實現程序
  3. 判斷影響因素
  4. 選擇測試數據,進行基準測試
  5. 得出結論
  6. </ol>

    附錄

    1. 看起來 getSampleSize 或 getSample 函數計算有偏差,不過在這里可以忽略這個問題。
    2. </ol>

      sample生成器

      var Chance = require('chance')
      var _ = require('lodash')

      var DATA_TYPES = [ 'bool', 'character', 'floating', 'integer', 'natural', 'string',

      'Array',
      'Object',
      

      ]

      function getSample(size, sample, chance) { chance = chance || new Chance() sample = sample || {} var index, cursor, pick, type, key, value for (index=0, cursor=0; index<size; index++, cursor++) { pick = chance.integer({min: index, max: size-1}) switch(type = chance.pick(DATA_TYPES)) { case 'Array': value = getSample(pick - index, [], chance) index = pick break case 'Object': value = getSample(pick - index, {}, chance) index = pick break default: value = chancetype } key = sample.constructor.name === 'Array' ? cursor : chance.word() sample[key] = value } return sample }

      function getSampleSize(sample) { return .reduce(sample, function(sum, value, key) { if (.isArray(value)) { sum += getSampleSize(value) } else if (_.isObject(value)) { sum += getSampleSize(value) } return sum + 1 }, 0) }

      module.exports = { getSample: getSample, getSampleSize: getSampleSize, }</pre>

      單元測試

      describe('utility', () => {
          describe('prune', () => {
              it('do not touch primitive type', () => {
                  expect(prune(123)).to.deep.equal(123)
                  expect(prune('123')).to.deep.equal('123')
                  expect(prune(null)).to.deep.equal(null)
                  expect(prune([1, 2, '3'])).to.deep.equal([1, 2, '3'])
                  expect(prune({foo: 'bar'})).to.deep.equal({foo: 'bar'})
              })

          it('prune null value in object', () => {
              expect(prune({foo: 'bar', baz: null})).to.deep.equal({foo: 'bar'})
          })
      
          it('do not prune null in array', () => {
              expect(
                  prune([null, 'foo', null, 'bar', null])
              ).to.deep.equal([null, 'foo', null, 'bar', null])
          })
      
          it('complex json prune', () => {
              expect(
                  prune([
                      null,
                      {
                          'foo1': 'bar1',
                          'foo2': {
                              'foo3': ['bar3', null],
                              'foo': null,
                          },
                          'foo': null
                      },
                      null
                  ])
              ).to.deep.equal([
                  null,
                  {
                      'foo1': 'bar1',
                      'foo2': {
                          'foo3': ['bar3', null],
                      },
                  },
                  null
              ])
          })
      })
      

      })</pre></article>

      來自: https://cattail.me/tech/2016/01/12/how-to-benchmark.html

      </code></code></code></code></code>

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!