DEV Community

Cover image for Benchmarking Javascript
Forgacs Peter
Forgacs Peter

Posted on

Benchmarking Javascript

In the last few weeks, I've been quite active on coding challenge sites such as Code Wars, Hacker Rank, and Code signal.

After solving a problem, you can check out the most popular solutions.

Seeing how another person solved a particular problem is exciting and allows you to grow as a developer. But if you look through the most upvoted solutions, you can see a pretty concerning pattern.

Usually, the most upvoted programming challenge answers are not the fastest ones but the shortest ones.

Since these sites do not show the performance of your code, I've used Benchmark.js and Jsperf to see the actual performance of some of the top voted solutions and methods that are frequently reoccurring.

Benchmark.js runs each function multiple times and then returns the operations per second value. The bigger the value, the faster the function.

Creating an Array from a String

As the first example, let's look at reversing a string.

Here are 3 different methods of doing the same thing.

// version 1

"hello".split("").reverse();

// version 2

[..."hello"].reverse();

// version 3

Array.from("hello").reverse();

In this example, the string hello is turned into a character array then reversed.

Let's create a benchmark with random length arrays in Node.js.

const { Benchmark } = require("benchmark");

function generateRandomString(length) {
  var result = "";
  var characters =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  var charactersLength = characters.length;
  for (var i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }
  return result;
}

function generateRandomNumberBetween(min, max){
    return Math.floor(Math.random() * max) + min;
}

// data contains 100 random strings with lneght between 1 and 1000
const data = [];
for (let i = 0; i < 100; i++) {
    data.push(generateRandomString(generateRandomNumberBetween(1, 1000)));
}

const suite = new Benchmark.Suite();
suite.add("string.split()", function() {
    for (const str of data) {
        str.split("");
    }
});
suite.add("Object spread", function () {
    for (const str of data) {
        [...str];
    }
});
suite.add("Array.from()", function() {
    for (const str of data) {
        Array.from(str);
    }
});
suite.on("cycle", function(event) {
    console.log(String(event.target));
});
suite.on("complete", function() {
    console.log("Fastest is " + this.filter("fastest").map("name"));
})
suite.run();

Result:

string.split() x 7,777 ops/sec ±16.99% (89 runs sampled)
Object spread x 284 ops/sec ±2.89% (77 runs sampled)
Array.from() x 138 ops/sec ±1.48% (75 runs sampled)
Fastest is string.split()

As you can see, the simple split method is significantly faster than the Object spread method.

The test ran in Node.js on my laptop, but we can create a Jsperf test to validate the results in different browsers.
You can check out the test here.

Flattening an Array

let arr = [ [1, 2], [3, 4]];

// version 1

arr.reduce((acc, curr) => acc.concat(curr), []);

// version 2

arr.reduce((acc, curr) => [...acc, ...curr], []);

// version 3

[].concat(...arr);

Let's compare them.

const { Benchmark } = require("benchmark");

function generateRandomNumberBetween(min, max){
    return Math.floor(Math.random() * max) + min;
}

function generateTupleArray(length) {
    const tupleArray = [];
    for (let i = 0; i < length; i++) {
        tupleArray.push([generateRandomNumberBetween(1, 1e3), generateRandomNumberBetween(1, 1e3)]);
    }
    return tupleArray;
}

// Contains 100 arrays containing elements between 1 and 1000
const arrays = [];
for (let i = 0; i < 100; i++) {
    arrays.push(generateTupleArray(generateRandomNumberBetween(1, 1e3)))
}


const suite = new Benchmark.Suite();
suite.add("acc.concat(curr)", function() {
    for (const arr of arrays) {
        arr.reduce((acc, curr) => acc.concat(curr), []);
    }
});
suite.add("[...acc, ...curr]", function () {
    for (const arr of arrays) {
        arr.reduce((acc, curr) => [...acc, ...curr], []);
    }
});
suite.add("[].concat(...data)", function() {
    for (const arr of arrays) {
        [].concat(...arr);
    }
});
suite.on("cycle", function(event) {
    console.log(String(event.target));
});
suite.on("complete", function() {
    console.log("Fastest is " + this.filter("fastest").map("name"));
})
suite.run();

Result:

acc.concat(curr) x 11.13 ops/sec ±1.90% (32 runs sampled)
[...acc, ...curr] x 0.48 ops/sec ±9.61% (6 runs sampled)
[].concat(...data) x 442 ops/sec ±1.25% (85 runs sampled)
Fastest is [].concat(...data)

We can see that the fastest method is orders of magnitudes faster than the second one.

Partition problem

As a final example I've created a benchmark for the top 10 most upvoted answers for a particular problem.

You can check out the problem statement here

Result:

#1  x 4,288 ops/sec ±1.15% (87 runs sampled)
#2  x 11,726,715 ops/sec ±0.90% (88 runs sampled)
#3  x 25,793 ops/sec ±1.00% (88 runs sampled)
#4  x 15,749 ops/sec ±1.07% (86 runs sampled)
#5  x 144 ops/sec ±1.09% (79 runs sampled)
#6  x 8,761 ops/sec ±1.26% (86 runs sampled)
#7  x 1,021 ops/sec ±1.16% (84 runs sampled)
#8  x 4,574 ops/sec ±0.95% (88 runs sampled)
#9  x 181,853 ops/sec ±12.23% (81 runs sampled)
#10 x 20,143 ops/sec ±1.03% (83 runs sampled)
Fastest is #2

As you can see, the #1 most popular solution is one of the slowest.

Conclusion

Sometimes seemingly hacky solutions are viable. Other times they have similar or in worse cases, slower performance than standard solutions. Performance can vary between environments.

But one thing is for sure. If you want to make decisions based on data, you must know the tools to benchmark your code.

Latest comments (2)

Collapse
 
anwarr_________ profile image
Anwar

Can you tell me how to install benchmark.js?
I have no experience with any library or stuff relevant to node.js so I didn’t understand the documentation so I would be grateful if you helped me

Collapse
 
skeletor48 profile image
Mihaly Banfi

I am not sure if it answers your question:

In a node.js environment, you will need npm to be installed on your system but basically, it is preinstalled in node.js. (check the documentation on how npmjs.com/get-npm)

Go to your project folder. You have to initialize your project with the npm init command (use npm init -y to prevent further options). This command will generate a package.json file.

A package.json file: (source:docs.npmjs.com/creating-a-package-...)

  • lists the packages your project depends on
  • specifies versions of a package that your project can use using semantic versioning rules
  • makes your build reproducible, and therefore easier to share with other developers

From now on the only thing whenever you want to install a new library/node package into your project is to use the:
npm i [library name] command

Additional options here:

  • npm i [library name] -g -> this installs the library globally on your system without it the installation will live only inside your project
  • npm i [library name] --save-dev -> this installs the package/library only as a dev dependency

My advice here is to just simply use npm i benchmark command in your project folder
From now on you can use benchmark in every file of your project where you require it like this:
const Benchmark = require('benchmark');