styled-components icon indicating copy to clipboard operation
styled-components copied to clipboard

perf: avoid regexp for generate alphabetic name fn

Open H4ad opened this issue 1 year ago • 5 comments

Removing RegExp to improve the speed of this helper fn.

Results:

generateAlphabeticName V1: 14.510s
generateAlphabeticName V2: 3.515s
Result: nJkmV

Benchmark:

var AD_REPLACER_R = /(a)(d)/gi;
/* This is the "capacity" of our alphabet i.e. 2x26 for all letters plus their capitalised
 * counterparts */
var charsLength = 52;
/* start at 75 for 'a' until 'z' (25) and then start at 65 for capitalised letters */

var getAlphabeticChar = function (code) {
  let temp = code + (code > 25 ? 39 : 97);

  return String.fromCharCode(temp);
};

const D_LOWER_CHAR = 'd'.charCodeAt(0);
const D_CHAR = 'D'.charCodeAt(0);

const A_LOWER_CHAR = 'a'.charCodeAt(0);
const A_CHAR = 'A'.charCodeAt(0);

function ensureDashForAD(a, b) {
  if (a.length === 0 || b.length === 0) {
    return a + b;
  }

  const lastBChar = b.charCodeAt(b.length - 1);

  if (lastBChar !== D_CHAR && lastBChar !== D_LOWER_CHAR) {
    return a + b;
  }

  const lastAChar = a.charCodeAt(a.length - 1);

  if (lastAChar !== A_CHAR && lastAChar !== A_LOWER_CHAR) {
    return a + b;
  }

  return a + '-' + b;
}

function generateAlphabeticNameV1(code) {
  let name = '';
  let x;

  /* get a char and divide by alphabet-length */
  for (x = Math.abs(code); x > charsLength; x = (x / charsLength) | 0) {
    name = getAlphabeticChar(x % charsLength) + name;
  }

  return (getAlphabeticChar(x % charsLength) + name).replace(AD_REPLACER_R, '$1-$2');
}

/* input a number, usually a hash and convert it to base-52 */
function generateAlphabeticNameV2(code) {
  var name = '';
  var x;
  /* get a char and divide by alphabet-length */
  for (x = Math.abs(code); x > charsLength; x = (x / charsLength) | 0) {
    name = ensureDashForAD(getAlphabeticChar(x % charsLength), name);
  }

  return ensureDashForAD(getAlphabeticChar(x % charsLength), name);
}

let result = '';

console.time('generateAlphabeticName V1')
for (let i = 0; i < 1e8; i++) {
  result = generateAlphabeticNameV1(i);
}
console.timeEnd('generateAlphabeticName V1')

console.time('generateAlphabeticName V2')
for (let i = 0; i < 1e8; i++) {
  result = generateAlphabeticNameV2(i);
}
console.timeEnd('generateAlphabeticName V2')

console.log('Result:', result);

H4ad avatar May 22 '24 00:05 H4ad

Perf for this library sometimes doesn't work the way you'd expect in my experience.

Screenshot 2024-07-17 at 11 08 32 AM

quantizor avatar Jul 17 '24 15:07 quantizor

Which value represents this PR?

H4ad avatar Jul 18 '24 22:07 H4ad

Ok I found a discrepancy in the benchmark code that I fixed in main and we can properly test this now

quantizor avatar Jul 19 '24 04:07 quantizor

Ran it again and the perf is pretty much the same between latest and this branch

quantizor avatar Jul 19 '24 04:07 quantizor

I know for sure this will be faster but maybe the impact on the benchmark is not much relevant, so is up to you to merge.

At least when I profiled some time ago, I was able to remove this function from the profile chart with this change, my benchmark was just rendering the same box multiple times.

H4ad avatar Jul 19 '24 12:07 H4ad