In my previous blog post, I talked about using the Array.some method as a handy tool for looping over “some” array elements until a condition is satisfied1. Well, once in a while, you’re going to come across an article which is critical of “new-fangled” Array methods. It’s a known issue that some of these array methods perform poorly in comparison to the “old-fashioned” for loop.

It’s not that people don’t like the new methods. In the article mentioned above, Yotam Kadishay says they make JavaScript “more functional”, and writing code is “more fun and smooth” with the addition of these new methods. But if your code runs like a dog, no one is going to really care about how much fun you had writing it, will they?

Mr. Kadishay wrote a good article, and you should read it! He performance tested some of the newer Array methods, and found they tend to run slower than the older, tried and true ones. But he didn’t discuss the performance of the Array.some method. Array.some is actually not so new-fangledit has been around since JavaScript 1.6 (2006).

So I’m going to performance test the implementation of Array.some, which is what I used in implementing my startsWithVowel method I wrote in my previous post1. For test purposes, I’ve snagged Peter Norvig’s list of 100,000 words – they are all uppercase. I saved this list to my /tmp directory.

To be fair, I might want to run my performance test with a list of lowercased words, a mix of cases and so on. But for now let’s just work with this list.

I’m going to test this in Node.js. I use nvm (Node Version Manager) to use whichever version of node I’m interested in testing. I’ll test using node 8.16. I have node installed on a development environment running Ubuntu 16.04. To run my test, I fire up a node command line as follows:

nvm use 8.16
node
>

Then I just copy and paste the code below into the command line and look at the results:

/* Displays results */
var finishAndPrint = function(words) {
    console.log("words length: " + words.length);
    console.log("First word: " + words[0]);
    console.log("Last word: " + words[words.length-1]);
};

/* This test function is guaranteed to never match a word in the Norvig list of words */
var testFunction = function(item) {
    return item === "gsdjkoeu";
};

/* A performance test using the Array.some method: */
var performanceTestSome = function(words) {
    const {performance} = require('perf_hooks');
    let t0 = performance.now();
    let result = words.some(function(el, i) {
        return testFunction(el);
    });
    let t1 = performance.now();
    console.log("The some method took " + (t1 - t0) + " milliseconds and the result was " + result + ".");
};

/* A performance test using a simple for loop: */
var performanceTestForLoop = function(words) {
    const {performance} = require('perf_hooks');
    let len = words.length;
    var result = false;
    let t0 = performance.now();
    for (var i = 0; i < len; i++) {
        if (testFunction(words[i])) {
            // This code is necessary so the function does the same thing as the "some" method
            result = true;
            break;
        }
    }
    let t1 = performance.now();
    console.log("The for loop method took " + (t1 - t0) + " milliseconds and the result was " + result + ".");
};

/* Read the words: */
fs.readFile('/tmp/count_1w100k.txt', {encoding: 'utf-8'}, function(err, data) {
    var words = [];
    var lines = data.split('\n');
    lines.forEach(function(row, i) {
        var items = row.split('\t');
        if (items[0].length > 0) {
            words.push(items[0]);
        }
    });
    finishAndPrint(words);
    performanceTestSome(words);
    performanceTestForLoop(words);
    performanceTestForLoop(words);
    performanceTestSome(words);
});

Here’s the output:

> words length: 100000
> First word: THE
> Last word: PGY
> The some method took 2.5111299753189087 milliseconds and the result was false.
> The for loop method took 1.983236014842987 milliseconds and the result was false.
> The for loop method took 0.6664620041847229 milliseconds and the result was false.
> The some method took 1.9726130068302155 milliseconds and the result was false.

Huh, there’s some kind of optimization going on in that for loop. I didn’t investigate. I’m assuming the first run is more accurate. It looks like the for loop is slightly more performant than the some method.

I repeated the test in a couple of different ways, because I wanted to make sure that the order of calling didn’t matter. In each case, I exited the node command line using .exit and restarted it. Here’s another example output, in which I called the for loop method twice followed by the some test. In this case, it looks as if the performance difference is negligible on the first run of each method. However, it continues to appear that there’s some optimization which is done when the for loop method is used the second time, but not when some is used.

> words length: 100000
> First word: THE
> Last word: PGY
> The for loop method took 2.146267980337143 milliseconds and the result was false.
> The for loop method took 0.590969979763031 milliseconds and the result was false.
> The some method took 2.074671983718872 milliseconds and the result was false.
> The some method took 1.9943189918994904 milliseconds and the result was false.

Since I’ve got my code here and explained the methods I used, together with the environment, you can repeat the tests for yourself. I think it’s important to give as much information about your methods and environment when running performance tests. And if I were going to get more serious, I’d probably do some statistics, maybe run this test multiple times, and do a chi-squared test or some sort of test to make sure that my results are not a fluke.

Before you consider performance testing, think about whether performance will ever truly be an issue. Is it worth performance testing when you’re building a prototype for a startup and the code may never even get used by more than a few people? Is it worth sacrificing readability and maintainability in order to run the most performant code? Robert Harvey wrote this comment in response to a Stack Overflow question about performance testing Node.js:

you can have slow code run forever where a user won’t notice it, and pretty fast code running where they do, and they will complain about the pretty fast code not being fast enough. Or that your request to your server API took 220ms. Or something else like that. The point remains that if you take a profiler out and go looking for work to do, you will find it, but it may not be the work your users need.

It is probably true that performance is more important when writing server-side code. Your server’s going to be fielding a bunch of requests from multiple users; whereas the browser client is just dealing with “one user” at a time. Of course, you want good performance on the client side as well. You may want to play it safe and use for loops everywhere. That may be overkill 🙂 You’d still better know how Array methods work, and know how to read them, because there are plenty of developers and companies which are not nearly that strict about it.

In my next blog post, I’ll run the same test using JavaScript on the client.