bumblehead.com{JSON:Feed}
  • blog
  • media
  • links
  • about

what's faster

  • bumblehead
  • blog
  • 2024-10-12

Interesting benchmarks for writing performance-sensitive javascript. Benchmark's here can be used with node's official benchmark.js scripts. The last number is the rate of operations measured in ops/sec (higher is better).

note: node's common.js benchmark file uses one or two commonjs-specific things and those must be updated before the benchmark will run successfully


Checking for primitives, use strict equality

primitives-regexp-test-vs-strict-equality
import common from '../node-v22.9.0/benchmark/common.js'

const TYPEREGEXPLOOSE = 'regexp loose'
const TYPEREGEXPSTRICT = 'regexp strict'
const TYPEEQUALITY = 'equality'

const bench = common.createBenchmark(main, {
  n: [2000],
  mod: 3,
  type: [
    TYPEREGEXPLOOSE,
    TYPEREGEXPSTRICT,
    TYPEEQUALITY
  ]
})

async function main(conf) {
  // save then return this obj to try bypassing any
  // runtime optimisation identifying this code as unused
  let obj = ''

  const type = conf.type
  const mod = conf.mod
  const regexpLooseRe = /string|boolean|number/
  const regexpStrictRe = /^(string|boolean|number)$/

  if (type === TYPEREGEXPLOOSE) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = true
      else if (n === 2) n = i
      else n = 'value' + i

      obj += regexpLooseRe.test(n)
    }
    bench.end(conf.n)
  }

  if (type === TYPEREGEXPSTRICT) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = true
      else if (n === 2) n = i
      else n = 'value' + i

      obj += regexpStrictRe.test(typeof n)
    }
    bench.end(conf.n)
  }

  if (type === TYPEEQUALITY) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = true
      else if (n === 2) n = i
      else n = 'value' + i

      const t = typeof n
      obj += (t === 'string' || t === 'boolean' || t === 'number')
    }
    bench.end(conf.n)
  }

  return obj
}
const isPrimitiveEquality = t =>
  t === 'string' || t === 'boolean'
  || t === 'number'

// note: tests reuse memoized RegExp
const isPrimitiveRegExpLoose = t =>
  /string|boolean|number/.test(t)

const isPrimitiveRegExpStrict = t =>
  /^(string|boolean|number)$/.test(t)
Test Ops/sec
is primitive equality 5,455,715.95
is primitive RegExp, loose 3,841,802.27
is primitive RegExp, strict 3,429,425.85

Checking for plain object, use loose strict equality

object-loose-test-vs-strict-equality
import common from '../node-v22.9.0/benchmark/common.js'

const TYPEOBJLOOSE = 'object loose'
const TYPEOBJSTRICT = 'object strict'
const TYPEOBJPROTO = 'object proto'

const bench = common.createBenchmark(main, {
  n: [2000],
  mod: 3,
  type: [
    TYPEOBJLOOSE,
    TYPEOBJSTRICT,
    TYPEOBJPROTO
  ]
})

async function main(conf) {
  // save then return this obj to try bypassing any
  // runtime optimisation identifying this code as unused
  let obj = ''

  const getproto = Object.getPrototypeOf
  const proto = Object.getPrototypeOf({})
  const type = conf.type
  const mod = conf.mod

  if (type === TYPEOBJLOOSE) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = { anobj: true }
      else if (n === 2) n = ['array']
      else n = 'value' + i

      obj += n && typeof n === 'object'
        && !Array.isArray(n)
    }
    bench.end(conf.n)
  }

  if (type === TYPEOBJSTRICT) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = { anobj: true }
      else if (n === 2) n = ['array']
      else n = 'value' + i

      obj += n && typeof n === 'object'
        && !Array.isArray(n)
        && n instanceof Date === false
        && n instanceof RegExp === false
    }
    bench.end(conf.n)
  }

  if (type === TYPEOBJPROTO) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = { anobj: true }
      else if (n === 2) n = ['array']
      else n = 'value' + i

      obj += (getproto(n) === proto)
    }
    bench.end(conf.n)
  }

  return obj
}
const isObjLoose = n => n
  && typeof n === 'object'
  && !Array.isArray(n)

const isObjStrict = n => isObjLoose(n)
  && n instanceof Date === false
  && n instanceof RegExp === false

const isObjProto = ((getp, p = getp({})) =>
  obj => obj && (getp(obj) === p)
)(Object.getPrototypeOf) // slowest

const isObjProtoStr = ((toString, str = toString.({})) =>
  obj => obj && (toString(obj) === str)
)(Object.prototype.toString)
Test Ops/sec
is object, loose 2,709,289.20
is object, strict 2,751,311.69
is object, proto 2,631,139.27
is object, proto str 2,313,421.13

Replacing a matched end character, use endsWith

remove-endslash-regexp-vs-char
import common from '../node-v22.9.0/benchmark/common.js'

const TYPEREGEXP = 'regexp'
const TYPEENDSWITH = 'endsWith'
const TYPECHARAT = 'charAt'

const bench = common.createBenchmark(main, {
  n: [2000],
  mod: 3,
  type: [
    TYPEREGEXP,
    TYPEENDSWITH,
    TYPECHARAT
  ]
})

async function main(conf) {
  // save then return this obj to try bypassing any
  // runtime optimisation identifying this code as unused
  let obj = ''

  const type = conf.type
  const mod = conf.mod

  if (type === TYPEREGEXP) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = 'stringwith/'
      else if (n === 2) n = 'stringwithout'
      else n = 'value' + i

      obj += n.replace(/\/$/, '')
    }
    bench.end(conf.n)
  }

  if (type === TYPECHARAT) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = 'stringwith/'
      else if (n === 2) n = 'stringwithout'
      else n = 'value' + i

      obj += n.endsWith('/') ? n.slice(0, -1) : n
    }
    bench.end(conf.n)
  }

  if (type === TYPEENDSWITH) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = 'stringwith/'
      else if (n === 2) n = 'stringwithout'
      else n = 'value' + i

      obj += n.charCodeAt(n.length - 1) === 47 ? n.slice(0, -1) : n
    }
    bench.end(conf.n)
  }

  return obj
}
const removeEndSlashRegExp = n =>
  n.replace(/\/$/, '')

const removeEndSlashEndsWith = n =>
  n.endsWith('/') ? n.slice(0, -1) : n

const removeEndSlashCharAt = n =>
  n.charCodeAt(n.length - 1) === 47
    ? n.slice(0, -1) : n
Test Ops/sec
remove end slash, RegExp 1,333,148.47
remove end slash, endsWith 3,982,025.14
remove end slahs, charCodeAt 3,822,301.22

Checking if property in object

property-in-vs-property-lookup
import common from '../node-v22.9.0/benchmark/common.js'

const TYPELOOKUP = 'exists lookup'
const TYPEINOBJ = 'exists in obj'

const bench = common.createBenchmark(main, {
  n: [4000],
  mod: 3,
  type: [
    TYPELOOKUP,
    TYPEINOBJ
  ]
})

async function main(conf) {
  // save then return this obj to try bypassing any
  // runtime optimisation identifying this code as unused
  let obj = ''

  const type = conf.type
  const mod = conf.mod

  if (type === TYPELOOKUP) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = { anobj: true }
      else if (n === 2) n = ['array']
      else n = {}

      obj += n['anobj']
    }
    bench.end(conf.n)
  }

  if (type === TYPEINOBJ) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = { anobj: true }
      else if (n === 2) n = ['array']
      else n = {}

      obj += 'anobj' in n
    }
    bench.end(conf.n)
  }

  return obj
}
const propertyIn = obj =>
  'property' in obj

const propertyLookup = obj =>
  obj['property']
Test Ops/sec
property in obj 2,281,780.50
obj[property] 3,030,011.51

Array destructuring vs array lookup

array-destructure-vs-array-lookup
import common from '../node-v22.9.0/benchmark/common.js'

const TYPEDESTRUCTURE = 'destructure'
const TYPELOOKUP = 'lookup'

const bench = common.createBenchmark(main, {
  n: [2000],
  mod: 3,
  type: [
    TYPEDESTRUCTURE,
    TYPELOOKUP
  ]
})

async function main(conf) {
  // save then return this obj to try bypassing any
  // runtime optimisation identifying this code as unused
  let obj = ''

  const type = conf.type
  const mod = conf.mod

  const arrayLookup = arr => {
    const val0 = arr[0]
    const val1 = arr[1]
    const val2 = arr[2]

    return val1 + val0 + val2
  }

  const arrayDestructure = arr => {
    const [val0, val1, val2] = arr

    return val1 + val0 + val2
  }

  if (type === TYPELOOKUP) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = ['arg' + n, n, i]
      else if (n === 2) n = ['arg' + n, i, n]
      else n = ['value' + i, i, i]

      obj += arrayLookup(n)
    }
    bench.end(conf.n)
  }

  if (type === TYPEDESTRUCTURE) {
    bench.start()
    for (let i = conf.n; i--;) {
      let n = (mod ? i % mod : i)
      if (n === 1) n = ['arg' + n, n, i]
      else if (n === 2) n = ['arg' + n, i, n]
      else n = ['value' + i, i, i]

      obj += arrayDestructure(n)
    }
    bench.end(conf.n)
  }

  return obj
}
const arrayLookup = arr => {
  const val0 = arr[0]
  const val1 = arr[1]
  const val2 = arr[2]

  return val1 + val0 + val2
}

const arrayDestructure = arr => {
  const [val0, val1, val2] = arr

  return val1 + val0 + val2
}
Test Ops/sec
array lookup 1,200,048.00
array destructure 708,328.67
tags
  • software
older
callbacks win
  • bumblehead
  • 2024-09-22

© bumblehead

0.2.0site-map