Skip to content

Instantly share code, notes, and snippets.

@0scarB
Last active June 25, 2024 19:42
Show Gist options
  • Save 0scarB/e561bc3819edf909b915ecbdfa406948 to your computer and use it in GitHub Desktop.
Save 0scarB/e561bc3819edf909b915ecbdfa406948 to your computer and use it in GitHub Desktop.
JavaScript large string concat benchmark (v8 + NodeJS)
#!/usr/bin/env node
const { Buffer } = require('node:buffer');
function main() {
console.log(`NodeJS version = ${process.versions.node}`);
console.log(`V8 version = ${process.versions.v8}`);
console.log("---------------------------------------------------")
measureStringConcat();
}
function measureStringConcat() {
for (const strLen of [
1, // Probably 1-2 words in memory
8, // Probably 2 or more words in memory on (on 64-bit)
32, // Probably fits in a single L1 cache line
3072, // <4096 -> Probably fits into a single page
2*4096, // >4096 -> Probably fits into multiple pages
]) {
const RAND_STRS_N = 256;
const RAND_STRS_STRIDE = 13;
const placeholderStr = "\x00".repeat(strLen);
const randStrs = Array(RAND_STRS_N).fill(placeholderStr);
for (let i = 0; i < RAND_STRS_N; i++) {
let randStr = "";
for (let j = 0; j < strLen; j++) {
const randCharCode = Math.floor(Math.random()*127.99999);
randStr += String.fromCharCode(randCharCode);
}
randStrs[i] = randStr;
}
console.log(`concatenating strings with ${strLen} characters`);
let randStrIdx = 0;
for (const [strsN, reps] of [
[ 100, 100_000],
[ 1_000, 10_000],
[10_000, 1000],
]) {
console.log(` no. of strings = ${strsN}`)
console.log(` no. of reps = ${reps}`)
{
const timeLabel = " string concat with '+=' ";
console.time(timeLabel)
for (let i = 0; i < reps; i++) {
let strAgg = "";
for (let j = 0; j < strsN; j++) {
strAgg += randStrs[randStrIdx];
randStrIdx += RAND_STRS_STRIDE;
if (randStrIdx > RAND_STRS_N) randStrIdx -= RAND_STRS_STRIDE;
}
}
console.timeEnd(timeLabel);
}
{
const timeLabel = " string concat with '.concat' ";
console.time(timeLabel)
for (let i = 0; i < reps; i++) {
let strAgg = "";
for (let j = 0; j < strsN; j++) {
strAgg = strAgg.concat(randStrs[randStrIdx]);
randStrIdx += RAND_STRS_STRIDE;
if (randStrIdx > RAND_STRS_N) randStrIdx -= RAND_STRS_STRIDE;
}
}
console.timeEnd(timeLabel);
}
{
const timeLabel = " string concat with 'template literals' ";
console.time(timeLabel)
for (let i = 0; i < reps; i++) {
let strAgg = "";
for (let j = 0; j < strsN; j++) {
strAgg = `${strAgg}${randStrs[randStrIdx]}`;
randStrIdx += RAND_STRS_STRIDE;
if (randStrIdx > RAND_STRS_N) randStrIdx -= RAND_STRS_STRIDE;
}
}
console.timeEnd(timeLabel);
}
{
const timeLabel = " string concat with 'array join' ";
console.time(timeLabel)
for (let i = 0; i < reps; i++) {
const l = [];
for (let j = 0; j < strsN; j++) {
l.push(randStrs[randStrIdx]);
randStrIdx += RAND_STRS_STRIDE;
if (randStrIdx > RAND_STRS_N) randStrIdx -= RAND_STRS_STRIDE;
}
const strAgg = l.join("");
}
console.timeEnd(timeLabel);
}
{
const timeLabel = " string concat with 'array reduce' ";
console.time(timeLabel)
for (let i = 0; i < reps; i++) {
const l = [];
for (let j = 0; j < strsN; j++) {
l.push(randStrs[randStrIdx]);
randStrIdx += RAND_STRS_STRIDE;
if (randStrIdx > RAND_STRS_N) randStrIdx -= RAND_STRS_STRIDE;
}
const strAgg = l.reduce((agg, s) => agg + s, "");
}
console.timeEnd(timeLabel);
}
{
const timeLabel = " string concat by 'writing to buffer' ";
console.time(timeLabel)
for (let i = 0; i < reps; i++) {
const buf = Buffer.allocUnsafe(strLen*strsN);
let bufOffset = 0;
for (let j = 0; j < strsN; j++) {
buf.write(randStrs[randStrIdx], bufOffset);
bufOffset += strLen;
randStrIdx += RAND_STRS_STRIDE;
if (randStrIdx > RAND_STRS_N) randStrIdx -= RAND_STRS_STRIDE;
}
const strAgg = buf.toString();
}
console.timeEnd(timeLabel);
}
}
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment