Delve into JavaScript arrays — Evolution & Performance

Diving deep into JavaScript Array-evolution & Performance

Before I write this article, I should note that this article does not cover the basics of JavaScript arrays, nor does it teach syntax and usage. More on how arrays are stored in memory, optimizations, behavior differences due to different syntax, performance, and recent improvements.

By the time I got into JavaScript, I was already quite familiar with C/C++/C# and other languages. But like many C/C++ users, the first “date” with JavaScipt is not a happy one.

One of the main reasons I don’t like JavaScipt is its Array. JavaScript arrays are implemented as hash maps or dictionaries, so they are not contiguous. I think it’s a poor language: arrays don’t even work properly. But today, JavaScript and my understanding of JavaScript have changed considerably.

Why aren’t JavaScript arrays real arrays

Before I get into JavaScript, let me tell you what Array is.

An Array, “Array,” is a series of contiguous areas in memory that hold values. Note the word “continuous”, which is crucial.

The figure above shows an array stored in memory. To store four 4-bit elements, a total of 16 bit storage areas are required, each element in the same order.

Suppose I declare tinyInt ARr [4], which occupies a strip of storage areas starting at position 1201. At some point I try to read a[2], then I just do a simple calculation to find the position of A [2]. For example 1201+(2X4) and then read directly from position 1209.

In JavaScript, arrays are hash maps. It can be implemented through a variety of data structures, one of which is a linked list. So if you declare var arr = new Array(4) in JavaScript; It produces a structure similar to the one above. Therefore, if you want to read a[2] somewhere in the program, it must start tracing a[2] from position 1201.

This is where JavaScript arrays differ from real arrays. Obviously the math takes less time than the list traversal. When you have a long array, it’s hard.

JavaScript array evolution

Remember how jealous we used to be if a friend’s computer had 256MB of RAM? But now, 8GB of RAM is the norm.

Like it, JavaScript is a language that has evolved a lot. Thanks to V8, SpiderMonkey, TC39, and the growing number of Web users, the world has become addicted to JavaScript. With such a large user base, performance improvements are imperative.

These days, JavaScript engines already allocate contiguous storage space for arrays of the same data type. A good developer always keeps arrays of the same data type so that the just-in-time compiler (JIT) can read arrays by evaluation just like a C compiler.

However, if you want to insert different types of elements into an array of the same type, the JIT will destroy the entire array and rebuild it the way it was before.

So, if you don’t write junk code, JavaScript Array objects maintain a real Array, which is a great thing for modern JS developers.

In addition, there are other improvements to arrays in ES2015/ES6. TC39 decided to introduce typed arrays in JavaScript, so we now have an ArrayBuffer.

An ArrayBuffer has a chunk of contiguous storage that you can use to do whatever you want with it. However, working directly with memory involves very low-level operations and is quite complex.

So we have Views to deal with ArrayBuffer. There are already some views available, and more will be added in the future:

var buffer = new ArrayBuffer(8);
var view  =  new Int32Array(buffer);
view[0]=100;
Copy the code

If you want to know more about typed arrays, check out the MDN documentation

Typed arrays perform well and are very efficient. WebGL developers often face performance problems due to a lack of efficient ways to handle binary data, so typed arrays were introduced. You can also use SharedArrayBuffer to share memory data across multiple Web-workers to improve performance.

Is it surprising? Starting with a simple hash map, we are now talking about Sharedarray buffers.

Old arrays vs typed arrays – performance

We’ve talked a lot about JavaScript array improvements. Now look at the benefits of typed arrays. I ran some small tests on the Mac using Node.js 8.4.0:

Old array – Insert

var LIMIT = 10000000;
var arr = new Array(LIMIT);
console.time('Array insertion time');
for(var i=0;i<LIMIT; i++) {
    arr[i]=i;
}
console.timeEnd('Array insertion time');
Copy the code

Time required: 55ms

Typed array – Insert

var LIMIT = 10000000;
var buffer = new ArrayBuffer(LIMIT * 4);
var arr = new Int32Array(buffer);
console.time("ArrayBuffer insertion time");
for (var i = 0; i &lt; LIMIT; i++) {
    arr[i] = i;
}
console.timeEnd("ArrayBuffer insertion time");
Copy the code

Time required: 52ms

Oh, my god. What do I see? Old arrays have the same performance as ArraryBuffers, right? Not at all. As a reminder, I mentioned earlier that compilers now allocate contiguous storage for arrays of consistent type. So in the first example, even though I’m using new Array(LIMIT), it still maintains a typed Array.

Let’s change the first example to an array of inconsistent types to see if performance changes.

Old array – insert (type inconsistent)

var LIMIT = 10000000;
var arr = new Array(LIMIT);
arr.push({a: 22});
console.time('Array insertion time');
for(var i=0;i<LIMIT; i++) {
    arr[i]=i;
}
console.timeEnd('Array insertion time');
Copy the code

Time required: 1207ms

I inserted an expression in the third line above to make the array type inconsistent, and everything else is exactly the same. But there was a huge change in performance: a full 22 times slower.

Old array – read

var arr = new Array(LIMIT);
arr.push({a: 22});
for (var i = 0; i &lt; LIMIT; i++) {
    arr[i] = i;
}
var p;
console.time("Array read time");
for (var i = 0; i &lt; LIMIT; i++) {
    //arr[i] = i;
    p = arr[i];
}
console.timeEnd("Array read time");
Copy the code

Time required: 196ms

Typed array – read

var LIMIT = 10000000;
var buffer = new ArrayBuffer(LIMIT * 4);
var arr = new Int32Array(buffer);
console.time("ArrayBuffer insertion time");
for (var i = 0; i &lt; LIMIT; i++) {
    arr[i] = i;
}
console.time("ArrayBuffer read time");
for (var i = 0; i &lt; LIMIT; i++) {
    var p = arr[i];
}
console.timeEnd("ArrayBuffer read time");
Copy the code

Time required :27ms

conclusion

The introduction of typed arrays in JavaScript was a huge step forward, Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array is a typed array view sorted by the native number of bytes. You can also look at DataView and create your own View window. Hopefully, more DataView libraries will be available in the future to make ArrayBuffer easier to use.

JavaScript makes great improvements to arrays. Now they are fast, efficient and smart enough to allocate memory.

reference

  1. Is JavaScript really interpreted or compiled language?
  2. Create / filter an array to have only unique elements in it
  3. Object.entries() & Object.values() in EcmaScript2017 (ES8) with examples
  4. Import vs require — ESM & commonJs Module differences
  5. A deep dive into ember routers – Ember.js Tutorial part 5
  6. Myths and Facts of JavaScript