Category Archives: Performance

How to test networking latency in React Native

In my previous post, I tested my React Native app to see what would happen if a fetch to get data from a URL resulted in an error. In my experience, URLs don’t just fail with a 404 error, however. Sometimes there’s a time delay in getting a response from a URL. This is sometimes called latency or a lag.

I wanted to see how my app would behave if the URL that it fetched had a long delay in responding. But I didn’t have a URL that would reliably take a long time to respond! How could I mimic this situation? That’s what I’ll address in this blog post.

My app does the fetch in the componentDidMount method, and then sets state to update the display, like this:

componentDidMount() {
    return fetch('https://facebook.github.io/react-native/movies.json')
        .then((response) => response.json())
        .then((responseJson) => {
            this.setState({
                isLoading: false,
                dataSource: responseJson.movies,
            });
        })
        .catch((error) => {
            // TODO FIXME replace the red screen with something informative.
            console.error(error);
        });
}

My initial attempt at adding a delay was to set a timeout around the setState call, but this resulted in an error. To simplify the code and make it easier to test, I moved the call to setState into a separate, reusable chunk of code, like this:

setMovieState(movies) {
    this.setState({
        isLoading: false,
        dataSource: movies,
    });
}

So then my componentDidMount becomes:

componentDidMount() {
    return fetch('https://facebook.github.io/react-native/movies.json')
        .then((response) => response.json())
        .then((responseJson) => {
            this.setMovieState(responseJson.movies);
        })
        .catch((error) => {
            // TODO FIXME replace the red screen with something informative.
            console.error(error);
        });
}

That looks a little cleaner, but I still need to do something to introduce a lag.

Let me write a new little piece of code which calls the method setMovieState after a specified delay:

handleMoviesResponse(movies, delay) {
    if (delay && delay > 0) {
        const timer = setTimeout(function () {
            this.setMovieState(movies);
        }.bind(this), delay);
    } else {
        this.setMovieState(movies);
    }
}

If there’s no delay, the state will be set immediately. If the method is called with a delay, then the state is not updated until after the input delay.

If you’re wondering what bind(this) is about, you may not be too familiar with JavaScript. A short answer is that the anonymous function that is passed to setTimeout uses this inside it, and that function needs to know what this is (this is my app which contains the method setMovieState.)

Finally, instead of calling setMovieState from componentDidMount, I call handleMoviesResponse, like this:

this.handleMoviesResponse(responseJson.movies, 5000);

Now that I’ve done this, I can see that when my app opens, I just see a plain white screen for 5 seconds, and then I see my movie titles dropdown (Picker) appear at the top of the page. It’s not a crash, but it seems like a bad user experience. In my next post, I’ll look at how to fix that.

blank screen when there’s network latency

Got comments? Send them to me in an email at fullstackdev@fullstackoasis.com. If you found this interesting, go ahead and click the subscribe button above. I write a new post about once a week.

About those JavaScript performance tests

In my last couple of articles, I wrote about some simple performance tests to compare Array.some with a for loop. I noticed that the for loop ran substantially faster the second time it was called in my process, while Array.some did not.

This got me curious. I wanted to know what was happening, so I dug around a little bit. It’s the kind of behavior that can be pretty tough to track down. I started with freenode for #Node.js. I described what I’d seen and got some feedback. I didn’t find a definitive answer, although I did get a clue. I was told essentially that “microbenchmarking” is pointless, and one should just write maintainable code, and let the JIT compiler worry about performance. I was directed to Mr Aleph‘s website. Mr Aleph is a compiler engineer and enthusiast, and I found a lot of interesting articles at his site.

In 2012 already, he had written a microbenchmarks fairy tale. It turns out that the V8 JavaScript engine compiler performs various optimizations that can produce misleading benchmarks. As an example, he talks about LICM (“loop-invariant code motion”). Oh god, the jargon! But he gives an example, which I’ll explain now.

Prior to working with JavaScript, I was a Java coder. In Java, one commonly writes for loops like this: for (int i = 0; i < arr.length; i++) {...}.

When I was getting started using JavaScript, I was told to assign Array.length to a variable outside the loop, and then reference the variable in the iteration, like this:

var len = arr.length;
for (var i = 0; i < len; i++) {
    ...
}

The reason given was that JavaScript is an interpreted language, and it will evaluate Array.length on every iteration over the loop! Sounds wasteful to me. So I’ve been dutifully, manually setting a variable to Array.length outside my loop ever since then.

Java developers don’t need to do this, because Java is a compiled language. The compiler performs optimizations behind the scenes which basically do what I was doing manually in JavaScript.

Flash forward to 2008-ish, about 10 years ago, when Google Chrome was launched, integrated with the V8 engine. If you thought I’d gotten off-track, I’m now back on it. According to Mr. Aleph, the V8 engine uses LICM, and LICM performs the optimization I’ve been doing manually in assigning Array.length to a variable outside the loop. And that’s just the tip of the optimization iceberg.

If you want to read more about the V8 engine, there’s an excellent series about it written by Alexander Zlatkov in 2017. Start with How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code. You should also take a good look at Mr. Aleph‘s articles.

Once JavaScript is not strictly being interpreted, trying to benchmark and optimize your code manually is probably just a mistake. Mr. Aleph’s conclusion, in 2012, was

The time when you could have easily guessed the cost of a single operation by simple observation is over. If you want to know something then sometimes the only way is to go and actually learn it from the inside out.

Mr. Aleph, “microbenchmarks fairy tale“, 2012

My own conclusion is that you should not think you are going to outsmart an optimizing compiler. Just write code which is readable. If, at some point, you notice bottlenecks, that’s when it makes sense to dig for performance improvements.

But I’ll still move Array.length outside of my iterators 😉

A performance test of the JavaScript Array.some function in Chrome browser

Previously, I performance-tested the Array.some function in Node.js version 8.16. Node.js is usually used for running microservices as a backend on a server. It’s not identical to JavaScript running client-side, in the browser. So it’s also interesting to run the same kind of performance test in a browser to see if the results apply there, too.

The source code for this test is similar to what it was in my previous test using Node.js. There are some necessary differences. I wanted to use the same word list, but I didn’t want to fiddle with trying to get the browser to read from a file, which is complex at best and impossible at worst (for security reasons).

Instead, I decided to use XMLHttpRequest to grab the word list I’d used previously, which was handily available on a web page.

However, you can’t just open Chrome browser and run the code below. If you try it (and you might want to), you’ll see this error message:

Access to XMLHttpRequest at 'https://norvig.com/ngrams/count_1w100k.txt' from origin 'chrome-search://local-ntp' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Oops! The solution is to first surf to Norvig’s page, then open the JavaScript console with Ctrl-Shift-J, paste in the code below, and call runTest().

/* 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) {
    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) {
    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 words from a URL and return the list of words */
var readTheWordsFromURL = function() {
    let myURL = "https://norvig.com/ngrams/count_1w100k.txt";
    var xmlHttp = null;
    xmlHttp = new XMLHttpRequest();
    xmlHttp.open( "GET", myURL, false );
    xmlHttp.send( null );
    return xmlHttp.responseText;
};

var runTest = function() {
    var data = readTheWordsFromURL();
    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);
    performanceTestForLoop(words);
    performanceTestForLoop(words);
    performanceTestSome(words);
    performanceTestSome(words);
};

runTest();

I did this in Chrome browser Version 75.0.3770.90 (Official Build) (64-bit), on my Ubuntu 16.04 desktop. I used private mode to make sure no other tabs, plugins, etc. would interfere with the test.

These were the results the first time I ran the code:

words length: 100000
VM18:4 First word: THE
VM18:5 Last word: PGY
VM18:36 The for loop method took 2.8300000121816993 milliseconds and the result was false.
VM18:36 The for loop method took 0.5550000059884042 milliseconds and the result was false.
VM18:20 The some method took 3.064999997150153 milliseconds and the result was false.
VM18:20 The some method took 2.6000000070780516 milliseconds and the result was false.

I repeated the test (closed the browser, reopened it, and ran the test), but this time changed the order of the tests. I ran the some test first:

...
VM18:20 The some method took 3.5149999894201756 milliseconds and the result was false.
VM18:20 The some method took 2.5599999935366213 milliseconds and the result was false.
VM18:36 The for loop method took 2.599999977974221 milliseconds and the result was false.
VM18:36 The for loop method took 0.670000008540228 milliseconds and the result was false.

Huh! I tried it again:

...
VM18:20 The some method took 3.5499999939929694 milliseconds and the result was false.
VM18:20 The some method took 2.634999982547015 milliseconds and the result was false.
VM18:36 The for loop method took 2.449999999953434 milliseconds and the result was false.
VM18:36 The for loop method took 0.5299999902490526 milliseconds and the result was false.

And again:

...
VM18:36 The for loop method took 2.9750000103376806 milliseconds and the result was false.
VM18:36 The for loop method took 0.5549999768845737 milliseconds and the result was false.
VM18:20 The some method took 3.939999995054677 milliseconds and the result was false.
VM18:20 The some method took 2.789999998640269 milliseconds and the result was false.

I ran another test case, just running the some method only. The result took 3.429999982472509 milliseconds. A test with just the for loop running once took 2.905000001192093 milliseconds.

The only conclusion I can draw is that Chrome doesn’t quite perform as well when running some instead of a simple for loop. The difference is not huge, and there’s some variation. This test runs over 100K words, and it still only takes a few milliseconds either way.

The results are similar to those in my previous post, which described a similar performance test using Node.js. This comes as no surprise, because Node.js runs on V8.

On the first run, it looks like a for loop performs a bit better than the some method. There’s some optimization on the second run. This is all déjà vu, if you read my last post.

Is it worth using a for loop everywhere to take advantage of that optimization? I think you’d really want to go deeper in your performance tests to understand the optimization, and make sure that it will be used before deciding to go with for loops everywhere. You will also want to be sure that it’s worth sacrificing code maintainability and readability in pursuit of some possibly minuscule performance boost.

It’s important to be aware of the potential for any performance impacts of special Array methods when architecting a new project or looking for bottlenecks in an existing one. But deciding never to use Array iterators just because you heard a vague rumor that they were not performant is unwise.

A performance test of the JavaScript Array.some function in Node.js

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 satisfied. 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-fangled – it 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 post. 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.