Skip to content

JIT-startsWith and endsWith #711

Closed
Closed

Description

@Uzlopak

I wonder if a startsWith JIT makes any sense for this or any project?

true startsWith x 95,549,028 ops/sec ±0.93% (95 runs sampled)
true startsWith jit x 1,409,981,216 ops/sec ±0.23% (95 runs sampled)
true endsWith x 98,114,558 ops/sec ±0.36% (99 runs sampled)
true endsWith jit x 199,258,515 ops/sec ±0.30% (97 runs sampled)
false startsWith x 125,452,965 ops/sec ±0.57% (94 runs sampled)
false startsWith jit x 1,409,183,606 ops/sec ±0.44% (99 runs sampled)
false endsWith x 126,028,596 ops/sec ±0.55% (95 runs sampled)
false endsWith jit x 198,250,186 ops/sec ±0.55% (98 runs sampled)

'use strict'

const assert = require('assert')
const Benchmark = require('benchmark')

const test = 'Lorem Ipsum I dolor'
const startsWithJit = startsWithJitGen('Lorem')
const endsWithJit = endsWithJitGen('dolor')
const Lorem = 'Lorem'
const dolor = 'dolor'

const lorem = 'lorem'
const Dolor = 'Dolor'

assert.ok(startsWithJit('ol') === false)
assert.ok(startsWithJit('or') === false)
assert.ok(startsWithJit('Dolor') === false)
assert.ok(startsWithJit('dolor') === false)
assert.ok(startsWithJit('lorem') === false)
assert.ok(startsWithJit('Lorem') === true)

assert.ok(endsWithJit('ol') === false)
assert.ok(endsWithJit('or') === false)
assert.ok(endsWithJit('Dolor') === false)
assert.ok(endsWithJit('dolor') === true)

new Benchmark.Suite()
  .add('true startsWith', function () { test.startsWith(Lorem) })
  .add('true startsWith jit', function () { startsWithJit(test) })
  .add('true endsWith', function () { test.endsWith(dolor) })
  .add('true endsWith jit', function () { endsWithJit(test) })
  .add('false startsWith', function () { test.startsWith(lorem) })
  .add('false startsWith jit', function () { startsWithJit(test) })
  .add('false endsWith', function () { test.endsWith(Dolor) })
  .add('false endsWith jit', function () { endsWithJit(test) })
  .on('cycle', function (event) { console.log(String(event.target)) })
  .run()

function startsWithJitGen(sequence) {
  const chain = []

  chain.push('(typeof value === \'string\')')
  chain.push(`(value.length >= ${sequence.length})`)

  for (let i = 0, il = sequence.length; i < il; ++i) {
    chain.push(`(value[${i}] === '${sequence[i]}')`)
  }

  const fnBody = 'return ' + chain.join(' && ')
  return new Function('value', fnBody)
}

function endsWithJitGen(sequence) {
  const chain = []

  chain.push('(typeof value === \'string\')')
  chain.push(`(value.length >= ${sequence.length})`)

  for (let i = 0, il = sequence.length; i < il; ++i) {
    if (i === 0) {
      chain.push(`(value[len] === '${sequence[i]}')`)
      continue
    }
    chain.push(`(value[len + ${i}] === '${sequence[i]}')`)
  }

  const fnBody = `const len = value.length - ${sequence.length}; return ` + chain.join(' && ')
  return new Function('value', fnBody)
}

Activity

  1. mcollina commented on Jul 7, 2022

    @mcollina
    Member

    I don't understand the question.

  2. Uzlopak commented on Jul 7, 2022

    @Uzlopak
    Author

    Is it worth in your opinion to create a project/package and to integrate into fastify to replace the calls of startsWith and endsWith calls in fastify with my implementation of startswith/endswith?

  3. mcollina commented on Jul 7, 2022

    @mcollina
    Member

    Do you know if/where startWith is a bottleneck?

  4. jsumners commented on Jul 7, 2022

    @jsumners
    Member

    I'm uneasy about using new Function() with arbitrary strings that .startsWith will be used against.

  5. jsumners commented on Jul 8, 2022

    @jsumners
    Member

    I don't think the complexity is necessary in any of those cases.

  6. Eomm commented on Mar 5, 2023

    @Eomm
    Member

    These endsWithJitGen and startsWithJitGen could be a nodejs module to use - in order to hide complexity tbh

  7. Uzlopak commented on Mar 5, 2023

    @Uzlopak
    Author

    @Eomm
    The performance gain comes imho by avoiding the string parsing every time you call startsWith or endsWith.

    E.g. in fastify/busboy we have the regex which is /^--boundary/ or so. So actually we could avoid that regex by using a function which checks the string line above.

  8. gurgunday commented on Apr 12, 2024

    @gurgunday
    Member

    nodejs/performance#159 (comment)

    Summary: indexOf gives similar improvements

    Moral of the story: avoid startsWith/endsWith

  9. Uzlopak commented on Apr 12, 2024

    @Uzlopak
    Author

    This is not correct. indexOf searches the whole string. So imagine 1 mb of string and at the end is the sequence you are looking for. In that case startsWith is faster as it will only check the beginning. The JIT variant is basically faster than startsWith as it does not have to process the "needle" everytime.

  10. gurgunday commented on Apr 12, 2024

    @gurgunday
    Member

    Ok, I corrected it, that's a non issue when we slice

    PTAL again

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions