How to Measuring JavaScript Functions’ Performance

View: 408    Dowload: 0   Comment: 0   Post by: hanhga  
Author: none   Category: Javascript   Fields: Other

9 point/3 review File has been tested

Performance has always played a crucial part in software. On the web, performance is even more important as our users can easily change website and visit one of our competitors if we offer them slow pages.

Introduction

Performance has always played a crucial part in software. On the web, performance is even more important as our users can easily change website and visit one of our competitors if we offer them slow pages. As professional web developers, we have to take this issue into account. A lot of old web performance optimization best practices, such as minimizing requests, using a CDN and not writing rendering blocking code, still apply today. However, as more and more web apps are using JavaScript, it’s important to verify that our code is fast.

Suppose that you have a working function but you suspect it’s not as fast as it could be, and you have a plan to improve it. How do you prove this assumption? What’s the best practice for testing the performance of JavaScript functions today? Generally, the best way to achieve this task is to use the built-in performance.now() function and measure the time before and after your function executes.

In this article we’ll discuss how to measure code execution time and techniques to avoid some common pitfalls.

Performance.now()

The High Resolution Time API offers a function, named now() that returns a DOMHighResTimeStampobject. It’s a floating point number that reflects the current time in milliseconds accurate to a thousandth of a millisecond. Individually, the number doesn’t add much value to your analysis, but a difference between two such numbers gives an accurate description of how much time has passed.

In addition to the fact that it is more accurate than the built-in Date object, it’s also “monotonic”. That means, in simple terms, that it’s not affected by the system (e.g. your laptop OS) periodically correcting the system time. In even simpler terms, defining two instances of Date and calculating the difference isn’t representative of the time that has passed.

The mathematical definition of “monotonic” is (of a function or quantity) varying in such a way that it either never decreases or never increases.

Another way of explaining it, is by trying to imagine using it around the times of the year when the clocks go forward or go back. For example, when the clocks in your country all agree to skip an hour for the sake of maximizing daytime sunshine. If you were to make a Date instance before clocks go back an hour, and another Date instance afterwards, looking at the difference it would say something like “1 hour and 3 seconds and 123 milliseconds”. With two instances of performance.now() the difference would be “3 seconds 123 milliseconds and 456789 thousands of a millisecond”.

In this section, I won’t cover this API in detail. So if you want to learn more about it and see some example of its use, I suggest you to read the article Discovering the High Resolution Time API.

Now that you know what the High Resolution Time API is and how to use it, let’s delve into some potential pitfalls. But before doing so, let’s define a function called makeHash() that we’ll use for the remainder of the article.

function makeHash(source) {
  var hash = 0;
  if (source.length === 0) return hash;
  for (var i = 0; i < source.length; i++) {
    var char = source.charCodeAt(i);
    hash = ((hash<<5)-hash)+char;
    hash = hash & hash; // Convert to 32bit integer
  }
  return hash;
}

The execution of such function can be measured as shown below:

var t0 = performance.now();
var result = makeHash('Peter');
var t1 = performance.now();
console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result);

If you run this code in a browser, you should see something like this :

Took 0.2730 milliseconds to generate: 77005292

Pitfall #1 – Accidentally Measuring Unimportant Things

In the example above, you can note that the only thing that we do between one performance.now() and the other is calling the function makeHash() and assigning its value to a variable result. This gives us the time it takes to execute that function and nothing else. This measurement could also be made as detailed below:

var t0 = performance.now();
console.log(makeHash('Peter'));  // bad idea!
var t1 = performance.now();
console.log('Took', (t1 - t0).toFixed(4), 'milliseconds');

But in this case, we would be measuring the time it takes to call the function makeHash('Peter') andhow long it takes to send and print that output on the console. We don’t know how long each of those two operations took. You only know the combined time. Also, the time it takes to send and print the output will vary greatly depending on the browser and even on what’s going on in it at that time.

Perhaps you’re perfectly aware that console.log is unpredictably slow. But it would be equally wrong to execute more than one function, even if each function does not involve any I/O. For example:

var t0 = performance.now();
var name = 'Peter';
var result = makeHash(name.toLowerCase()).toString();
var t1 = performance.now();
console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result);

Again, we won’t know how the execution time was distributed. Was it the variable assignment, thetoLowerCase() call, or the toString() call?

Pitfall #2 – Measuring only Once

Another common mistake is to make just one measurement, summarize the time taken and draw conclusions based on that. It’s likely to be totally different at different times. The execution time greatly depends on various factors:

  • Time for the compiler to warm up (e.g. time to compile the code into byte code)
  • The main thread being busy doing other things we didn’t realize were going on
  • Your computer’s CPU(s) being busy with something that slows down your whole browser

An incremental improvement is to execute the function repeatedly, like this:

var t0 = performance.now();
for (var i = 0; i < 10; i++) {
  makeHash('Peter');
}
var t1 = performance.now();
console.log('Took', ((t1 - t0) / 10).toFixed(4), 'milliseconds to generate');

The risk with this approach is that our browser’s JavaScript engine might make sub-optimizations which means that the second time the function is called with the same input, it can benefit from remembering the first output and simply use that again. To solve this issue, you can use many different input strings instead of repeatedly sending in the same input string (e.g. 'Peter'). Obviously, the problem with testing with different inputs is that naturally the function we’re measuring takes different amounts of time. Perhaps some of the inputs cause longer execution time than others.

Pitfall #3 – Relying too Much on the Average

In the last section we learned that it’s a good practice to run something repeatedly, ideally with different inputs. However, we have to remember that the problem with different inputs is that the execution might take much longer than all the other inputs. So let’s take a step back and send in the same input. Suppose that we send in the same input ten times and for each, print out how long that took. The output might look something like this:

Took 0.2730 milliseconds to generate: 77005292
Took 0.0234 milliseconds to generate: 77005292
Took 0.0200 milliseconds to generate: 77005292
Took 0.0281 milliseconds to generate: 77005292
Took 0.0162 milliseconds to generate: 77005292
Took 0.0245 milliseconds to generate: 77005292
Took 0.0677 milliseconds to generate: 77005292
Took 0.0289 milliseconds to generate: 77005292
Took 0.0240 milliseconds to generate: 77005292
Took 0.0311 milliseconds to generate: 77005292

Note how the very first time, the number is totally different from the other nine times. Most likely, that’s because the JavaScript engine in our browser makes some sub-optimizations and needs some warm-up. There’s little we can do to avoid that but there are some good remedies we can consider to prevent a faulty conclusion.

One way is to calculate the average of the last nine times. Another more practical way is to collect all results and calculate a median. Basically, it’s all the results lined up, sorted in order and picking the middle one. This is where performance.now() is so useful, because you get a number you can do whatever with.

Let’s try again but this time we’ll use a median function:

var numbers = [];
for (var i=0; i < 10; i++) {
  var t0 = performance.now();
  makeHash('Peter');
  var t1 = performance.now();
  numbers.push(t1 - t0);
}

function median(sequence) {
  sequence.sort();  // note that direction doesn't matter
  return sequence[Math.ceil(sequence.length / 2)];
}

console.log('Median time', median(numbers).toFixed(4), 'milliseconds');

Pitfall #4 – Comparing Functions in a Predictable Order

We’ve understood that it’s always a good idea to measure something many times and take the average. Moreover, the last example taught us that it’s preferable to use the median instead of the average.

Now, realistically, a good use of measuring function execution time is to learn which of several functions is faster. Suppose we have two functions that take the same type of input and yield the same result but internally they work differently.

Let’s say we want to have a function that returns true or false if a certain string is in an array of other strings, but does this case insensitively. In other words we can’t use Array.prototype.indexOfbecause it’s not case insensitive. Here’s one such implementation:

function isIn(haystack, needle) {
  var found = false;
  haystack.forEach(function(element) {
    if (element.toLowerCase() === needle.toLowerCase()) {
      found = true;
    }
  });
  return found;
}

console.log(isIn(['a','b','c'], 'B'));  // true
console.log(isIn(['a','b','c'], 'd'));  // false

Immediately we notice that this can be improved because the haystack.forEach loop always goes through all the elements even if we have an early match. Let’s try to write a better version using a good oldfor loop.

function isIn(haystack, needle) {
  for (var i = 0, len = haystack.length; i < len; i++) {
    if (haystack[i].toLowerCase() === needle.toLowerCase()) {
      return true;
    }
  }
  return false;
}

console.log(isIn(['a','b','c'], 'B'));  // true
console.log(isIn(['a','b','c'], 'd'));  // false

Now let’s see which one is the fastest. We do this by running each function 10 times and collecting all the measurements:

function isIn1(haystack, needle) {
  var found = false;
  haystack.forEach(function(element) {
    if (element.toLowerCase() === needle.toLowerCase()) {
      found = true;
    }
  });
  return found;
}

function isIn2(haystack, needle) {
  for (var i = 0, len = haystack.length; i < len; i++) {
    if (haystack[i].toLowerCase() === needle.toLowerCase()) {
      return true;
    }
  }
  return false;
}

console.log(isIn1(['a','b','c'], 'B'));  // true
console.log(isIn1(['a','b','c'], 'd'));  // false
console.log(isIn2(['a','b','c'], 'B'));  // true
console.log(isIn2(['a','b','c'], 'd'));  // false

function median(sequence) {
  sequence.sort();  // note that direction doesn't matter
  return sequence[Math.ceil(sequence.length / 2)];
}

function measureFunction(func) {
  var letters = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'.split(',');
  var numbers = [];
  for (var i = 0; i < letters.length; i++) {
    var t0 = performance.now();
    func(letters, letters[i]);
    var t1 = performance.now();
    numbers.push(t1 - t0);
  }
  console.log(func.name, 'took', median(numbers).toFixed(4));
}

measureFunction(isIn1);
measureFunction(isIn2);

We run that and get following output:

true
false
true
false
isIn1 took 0.0050
isIn2 took 0.0150

What the heck has just happened? The first function was three times faster. That was not supposed to happen!

The explanation is simple but subtle. The first function which uses haystack.forEach benefits from some low-level optimizations in the browser’s JavaScript engine that we don’t get when we use an array index technique. It proves our point: you never know until you measure it!

How to Measuring JavaScript Functions’ Performance

How to Measuring JavaScript Functions’ Performance Posted on 01-03-2016  Performance has always played a crucial part in software. On the web, performance is even more important as our users can easily change website and visit one of our competitors if we offer them slow pages. 3/10 408

Comment:

To comment you must be logged in members.

Files with category

  • JUnit 5 State Of The Union using java

    View: 633    Download: 0   Comment: 0   Author: none  

    JUnit 5 State Of The Union using java

    Category: Javascript
    Fields: Other

    1.5/3 review
    JUnit 5 has been under development for about 14 months now and the prototype is almost a year old. Time to summarize what happened so far, where the project stands, and where it’s going.

  • Getting Started with Dropwizard using java

    View: 736    Download: 0   Comment: 0   Author: none  

    Getting Started with Dropwizard using java

    Category: Javascript
    Fields: Other

    1.5/3 review
    Dropwizard is a framework for building RESTful web services in Java. In this tutorial we’re going to have a look at how to get started with developing a Dropwizard application by building a new service from scratch.

  • Build Query NULL Value in MySql

    View: 340    Download: 0   Comment: 0   Author: none  

    Build Query NULL Value in MySql

    Category: Javascript
    Fields: Other

    2.5/2 review
    Misunderstanding NULL is common mistake beginners do while writing MySql query. While quering in MySql they compare column name with NULL. In MySql NULL is nothing or in simple word it isUnknown Value so if you use comparison operator for NULL values...

  • Manage Your JavaScript Application State with MobX

    View: 406    Download: 0   Comment: 0   Author: none  

    Manage Your JavaScript Application State with MobX

    Category: Javascript
    Fields: Other

    2.25/2 review
    This article was peer reviewed by Michel Weststrate and Aaron Boyer. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

  • Build Bringing Pages to Life with the Web Animations API

    View: 364    Download: 0   Comment: 0   Author: none  

    Build Bringing Pages to Life with the Web Animations API

    Category: Javascript
    Fields: Other

    3/3 review
    This article is by guest author Dudley Storey. SitePoint guest posts aim to bring you engaging content from prominent writers and speakers of the JavaScript community.

  • How to Style Google Custom Search Manually

    View: 346    Download: 0   Comment: 0   Author: none  

    How to Style Google Custom Search Manually

    Category: Javascript
    Fields: Other

    0/0 review
    Website owners very often decide on using Google’s Custom Search Engine (GCSE) for searching through their content instead of using built-in and/or custom search functionality. The reason is simple – it’s much less work, and most often it does the...

  • Test React Components Using Jest

    View: 5116    Download: 0   Comment: 0   Author: none  

    Test React Components Using Jest

    Category: Javascript
    Fields: Other

    4.5/1 review
    This article is by guest author Jack Franklin. SitePoint guest posts aim to bring you engaging content from prominent writers and speakers of the JavaScript community.

  • Programming Question Reverse String without using String function

    View: 821    Download: 0   Comment: 0   Author: none  

    Programming Question Reverse String without using String function

    Category: Javascript
    Fields: Other

    4.5/1 review
    Write a program to reverse string without using string function. You don’t have to use any in-built string library function. This problem can be solved by multiple approaches. Let’s check it.

 

File suggestion for you

File top downloads

logo codetitle
Codetitle.com - library source code to share, download the file to the community
Copyright © 2015. All rights reserved. codetitle.com Develope by Vinagon .Ltd