Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: improve string operations #382

Merged
merged 1 commit into from
Dec 18, 2024
Merged

Conversation

gurgunday
Copy link
Contributor

@gurgunday gurgunday commented Dec 5, 2024

str.split('a').join('b') creates an intermediary array and is slower than .replaceAll, which immediately replaces the patterns without creating an array, .replaceAll has been available in Node.js since v15

Vanilla benchmark results:

Results:
VM142:51 ----------------------------------------
VM142:52 split().join() average time: 0.0020ms
VM142:53 replaceAll() average time: 0.0012ms
VM142:54 Difference: 0.0008ms
VM142:55 replaceAll() is faster by 40.00%

Results:
VM142:51 ----------------------------------------
VM142:52 split().join() average time: 0.0065ms
VM142:53 replaceAll() average time: 0.0034ms
VM142:54 Difference: 0.0031ms
VM142:55 replaceAll() is faster by 47.69%

Results:
VM142:51 ----------------------------------------
VM142:52 split().join() average time: 0.0416ms
VM142:53 replaceAll() average time: 0.0216ms
VM142:54 Difference: 0.0200ms
VM142:55 replaceAll() is faster by 48.08%
Benchmark
function generateRandomString(length) {
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let result = '';
  for (let i = 0; i < length; i++) {
      result += characters.charAt(Math.floor(Math.random() * characters.length));
  }
  return result;
}

function runBenchmark(iterations, stringLength) {
  const results = {
      splitJoin: [],
      replaceAll: []
  };
  
  const testStrings = Array(iterations).fill(null).map(() => {
      const str = generateRandomString(stringLength);
      return str.replaceAll('a', 'X').replaceAll('b', 'X').replaceAll('c', 'X');
  });
  
  console.log(`Running benchmark with ${iterations} iterations on strings of length ${stringLength}`);
  console.log('----------------------------------------');
  
  console.log('Testing split().join()...');
  for (let i = 0; i < iterations; i++) {
      const startTime = performance.now();
      testStrings[i].split('X').join('Y');
      const endTime = performance.now();
      results.splitJoin.push(endTime - startTime);
  }
  
  console.log('Testing replaceAll()...');
  for (let i = 0; i < iterations; i++) {
      const startTime = performance.now();
      testStrings[i].replaceAll('X', 'Y');
      const endTime = performance.now();
      results.replaceAll.push(endTime - startTime);
  }
  
  const splitJoinAvg = results.splitJoin.reduce((a, b) => a + b, 0) / iterations;
  const replaceAllAvg = results.replaceAll.reduce((a, b) => a + b, 0) / iterations;
  
  console.log('\nResults:');
  console.log('----------------------------------------');
  console.log(`split().join() average time: ${splitJoinAvg.toFixed(4)}ms`);
  console.log(`replaceAll() average time: ${replaceAllAvg.toFixed(4)}ms`);
  console.log(`Difference: ${Math.abs(splitJoinAvg - replaceAllAvg).toFixed(4)}ms`);
  console.log(`${splitJoinAvg < replaceAllAvg ? 'split().join()' : 'replaceAll()'} is faster by ${((Math.abs(splitJoinAvg - replaceAllAvg) / Math.max(splitJoinAvg, replaceAllAvg)) * 100).toFixed(2)}%`);
  
  return {
      splitJoinAvg,
      replaceAllAvg,
      difference: Math.abs(splitJoinAvg - replaceAllAvg),
      percentageDifference: ((Math.abs(splitJoinAvg - replaceAllAvg) / Math.max(splitJoinAvg, replaceAllAvg)) * 100)
  };
}

const smallTest = runBenchmark(1000, 100);
console.log('\n');
const mediumTest = runBenchmark(1000, 1000);
console.log('\n');
const largeTest = runBenchmark(1000, 10000);

Copy link
Collaborator

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@gurgunday
Copy link
Contributor Author

@ivan-tymoshenko ptal :)

Copy link
Collaborator

@ivan-tymoshenko ivan-tymoshenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina merged commit 4a2e5a7 into delvedor:main Dec 18, 2024
7 checks passed
@gurgunday gurgunday deleted the replaceall branch December 18, 2024 08:23
@Fdawgs
Copy link
Contributor

Fdawgs commented Dec 18, 2024

@gurgunday Is .replaceAll() slower than .replace(//g)?

@gurgunday
Copy link
Contributor Author

gurgunday commented Dec 18, 2024

I try to avoid RegExps in general, but the difference between them was insignificant when I last benched

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants