Designing for Performance (JavaScript on Steroids)

May 02, 2018 0 Comments

Designing for Performance (JavaScript on Steroids)

 

 

Web Apps are getting increasingly complex with the advancement of time, the need to provide users with cool features and the desire of companies to outpace each other among other reasons. The onus therefore is on the modern software engineer to design software that’s capable of making efficient use of resources in order to meet these demands and more. This article talks about tried and tested practices every JavaScript Developer should know. I’ll talk about performance enhancement techniques applicable to any JavaScript environment.

Environment Agnostic Performance Enhancement

Use Object, Array Literals and Primitive types over Constructors to instantiate Types Using Object and Array literals to creates objects is faster than any other way of creating these types. Literals are evaluated faster and even take up less space in your code and are also less cumbersome as compared to using their constructor counterparts.

let arr = [1, 2, 2], obj = { name : "Your Name", age : "Your Age", };

is preferred over

let arr = new Array();
arr[0] = 1;
arr[1] = 2;
arr[2] = 3; let obj = new Object();
obj["name"] = "Your Name";
obj.age = "Your Age";

In the same vein, using the constructor versions of primitive types at their instantiation is considered an ANTI PATTERN.

let boo = true,
str = "some text here",
num = 55,
nill = null,
und = undefined;
//TODO check whether there exists new Undefined and Null

is recommended over

let boo = new Boolean(true),
str = new String("some text here"),
num = new String(55),
//TODO check whether there exists new Undefined and Null

Avoid unecessary work and/or repeatition

One easy way to speed up your code will be to avoid doing anything that is not needed. If a piece of work is completed, DO NOT repeat it. This saves a lot of resources. The latter can be done using init-time branching. Here a piece of code only runs once at the initial execution of a script. For example, it doesn’t make sense to check if a feature exists anytime a function is called, it would be more appropriate to do this test only at the first run of the script.

let addEvt = document.addEventListener ?
function(el, type, handler){el.addEventListener(type, handler, false);}: //w3c
function(el, type, handler){el.attachEvent("on" + type, handler);}; let removeEvt = document.removeEventListener ?
function(el, type, handler){el.removeEventListener(type, handler, false);}: //w3c
function(el, type, handler){el.detachEvent("on" + type, handler);};

Another way to eliminate work repetition in functions is through lazy loading. Lazy loading means that no work is done until the first time information is necessary.

 function addHandler(el, type, handler) { //overwrite the existing function if (el.addEventListener) { //DOM2 Events addHandler = function(el, type, handler) {el.addEventListener(type, handler, false);}; } else { //IE addHandler = function(el, type, handler) {el.attachEvent("on" + type, handler);}; } //call the new function addHandler(el, type, handler); }

This function implements the lazy-loading pattern. The first time the function is called, a check is made to determine the appropriate way to attach the event handler. Then, the original function is overwritten with a new function that contains just the appropriate course of action. The last step during that first function call is to execute the new function with the original arguments. Each subsequent call to addHandler() avoids further detection because the detection code was overwritten by a new function.

Use the Fast parts of the language

Are there fast parts? yasss.gif These inlcude language constructs that run at a low level and are thus more quickly executed.

  • Bitwise Operators (&, |, ^, ~, >>, <<, >>>) These rarely used set of operators, perhaps because they are not well understood even to the point of being mistakened sometimes for boolean opeartors, are some of the fastest parts of the javaScript language. JavaScript numbers are all stored in IEEE-754 64-bit format. For bitwise operations, though, the number is converted into a signed 32-bit representation. Each operator then works directly on this 32-bit representation to achieve a result. Despite the conversion, this process is incredibly fast when compared to other mathematical and Boolean operations in JavaScript. If you’re unfamiliar with binary representation of numbers, JavaScript makes it easy to convert a number into a string containing its binary equivalent by using the toString() method and passing in the number 2. For example:
let num = 25,
console.log(num.toString(2)); //"11001"

Bitwise AND Returns a number with a 1 in each bit where both numbers have a 1

//bitwise AND
let result1 = 25 & 3; //1
console.log(result.toString(2)); //"1"

READ MORE about BITWISE operators here

  • Native Methods and Data Structures The methods that ship with the language and exsist before you ever wirte any javaScript code by yourself are implemented using a low level language (e.g C, C++) and thus run faster than their vanilla javaScript counterparts. A common mistake of inexperienced JavaScript developers is to perform complex operations in code when there are better performing versions available on the built-in objects. DO use the built-in methods anytime if they are available. DO NOT, for instance, write your own function to convert a string to lower case unless for learning purposes

Write Fast Evaluating Code

  • Speed up lookup (no pun intended) Your apps will enjoy performance boosts when you reduce the amount of time it takes to look up the value of a variable; To do this you first need to understand the effect of scopes on identifier lookup time. JavaScript prior to ES6 was function scoped and so to create a private variable, you need to define it in a function. Function objects have properties just like any other object, these include properties that can be accessed programmatically and other internal properties that are used by the JavaScript engine but are not accessible through code. One of these properties is [[Scope]], as defined by ECMA-262, Third Edition. The internal [[Scope]] property contains a collection of objects (The Scope Chain) representing the scope in which the function was created and determines the data that a function can access. Each object in the function’s scope chain is called a variable object, and each of these contains entries for variables in the form of key-value pairs. When a function is created, its scope chain is populated with objects representing the data that is accessible in the scope in which the function was created. Every call of a function creates another [[internal object]] called an execution context. An execution context defines the environment in which a function is being executed. Each execution context is unique to one particular execution of the function, and so multiple calls to the same function result in multiple execution contexts being created. The execution context is destroyed once the function has been completely executed. An execution context has its own scope chain that is used for identifier resolution. Each time a variable is encountered during the function’s execution, the process of identifier resolution takes place to determine where to retrieve or store the data. Identifier resolution isn’t free, as in fact no computer operation really is without some sort of performance overhead. The deeper into the execution context’s scope chain an identifier exists, the slower it is to access for both reads and writes. Consequently, local variables are always the fastest to access inside of a function, whereas global variables will generally be the slowest (optimizing JavaScript engines are capable of tuning this in certain situations) In view of this, DO create a local reference to any variable originally defined outside a function's local scope. BETTER STILL use pure functions and avoid slow lookups and side-effects altogether.

  • Faster loops Loops help handle repetitive stuff that would otherwise be done by hand. These constructs however can become the bane of application speed if not well used. loops usually have four parts namely, the starting point (counter initialisation), a condition, the incrementing or decrementing operation and the loop body. To Speed up a loop you have the option to optimize either of these parts of a loop. The condition is the first part you can optimise. You can do this by reducing the amount of expression evaluation done here at every pass of the loop. Doing things like lookup at this part of a loop is considered an ANTI PATTERN as it increases the amount of time it takes to start the execution of the loop body. If the evaluation of a part of the conditional expression will alwasy return the same value, then it makes sense to do the evaluation outside the loop and store the value in a variable. For instance

let arr = [5, 6, 14, 8, 9, 8];
for(let i = 0; i < arr.length; i++)
{ console.log(arr[i]);
}

for the example above every run of the loop will make a lookup for the length of the array "arr", which will be the same as long as the array is not mutated. A better option will be to store the value of the array length in a variable and read the value from there. This is called Memoisation. An example fixed code will look like:

let arr = [5, 6, 14, 8, 9, 8], len = arr.length;
for(let i = 0; i < len; i++)
{ console.log(arr[i]);
}

The above code will run considerably faster than the previous one.

The body of a loop can do with some optimisation too. A big RED FLAG is the creation of a function in a loop; Never do it. The fact that javaScript allows you to doesn't mean you should. Like Douglas Crockford says "... you are not paid to use any feature in the language" If you can reduce the number of statements in a loop body, you definitely should.

A well known mehtod for loop body optimisation is the use of Duff's Loop device unrolling This is a technique of unrolling loop bodies so that each iteration actually does the job of many iterations. Jeff Greenberg is credited with the first published port of Duff’s Device to JavaScript from its original implementation in C. A typical implementation looks like this:

//credit: Jeff Greenberg
let iterations = Math.floor(items.length / 8),
startAt = items.length % 8,
i = 0;
do { switch(startAt) { case 0: process(items[i++]); break; case 7: process(items[i++]); break; case 6: process(items[i++]); break; case 5: process(items[i++]); break; case 4: process(items[i++]); break; case 3: process(items[i++]); break; case 2: process(items[i++]); break; case 1: process(items[i++]); break; } startAt = 0;
} while (--iterations);

You should avoid using a loop if it's possible. For instance, if you loop through array elements in order to find a value, it would make sense to replace the array data structure with a Map or Object literal to eliminate the need to loop when a value has to be retrieved or searched for.

  • Faster Conditionals

    Optimising if-else

    When optimizing if-else, the goal is always to minimize the number of conditions to evaluate before taking the correct path. The easiest optimization is therefore to ensure that the most common conditions are evaluation first. Consider the following:

if (val < 5){ //do sth
}
else if (val > 5 && val < 10){ //do sth
}
else{ //do sth
}
Lookup Tables

We can sometimes avoid using if-else and switch altogether. When there are a large number of discrete values for which to test, both ifelse and switch are significantly slower than using a lookup table which can be created using arrays or regular objects in JavaScript. Accessing data from a lookup table is much faster than using if-else or switch, especially when the number of conditions is large. Consider the following:

switch(value)
{ case 0: return result0; case 1: return result1; case 2: return result2; case 3: default: return result4;
}

Even the amount of space the above code takes is not worth it

//define the array of results
var results = [result0, result1, result2, result3, result4, result5, result6, result7, result8, result9, result10]
//return the correct result
return results[value];

Find more JavaScript optimisation techniques in the following books: “High Performance JavaScript” by Nicholas C. Zakas “Mastering JavaScript High Performance Paperback by Chad R. Adams “Pro JavaScript Performance Monitoring and Visualisation” by Tom Barker


Tag cloud