Typed arrays

Over the years, JavaScript engines have become amazingly faster. However, simply being able to process data faster doesn't necessarily equate to being able to do more powerful things. Take WebGL, for example. Just because the browser now has the ability to understand OpenGL ES, it doesn't necessarily mean that it has all the tools we developers need to take advantage of that.

The good news is that the JavaScript language has also made some progress in order to satisfy this, and other needs that have come about. One such addition to JavaScript in recent years is a new data type: typed arrays. In general, typed arrays offer a structure similar to the array type already in JavaScript. However, these new arrays are much more efficient, and were designed with binary data in mind.

Why and how are typed arrays more efficient than regular arrays, you ask? Well, let's look at a trivial example, where all we do is traverse an array of integers the old way. Although most JavaScript engines don't particularly struggle to get this task done fairly fast, let us not overlook all the work the engine needs to do in order to do this.

var nums = [1, 2, 3, 4, 5];
for (var i = 0, len = nums.length; i < len; i++) {
   // ...
}

Since JavaScript is not strongly typed, the array nums is not restricted to holding data of any particular type. Furthermore, the nums array can store a different data type for each element in it. While this can sometimes be convenient for a programmer, the JavaScript engine needs to figure out where each element is stored, and what data type is being stored at said location. Contrary to what you may think, those five elements in the nums array may not be stored in a contiguous piece of memory, because, well, that's how JavaScript does it.

With typed arrays, on the other hand, each element in the array can only be an integer or a float. Based on the type of array we choose, we can have a different type of integer or float (signed, unsigned, 8, 16, or 32 bits), but every element in the array is always the same data type we decide to use (integer or float). This way, the browser knows precisely and instantly, where in memory element nums[3] is found which is at memory address nums + 3. This can be done because typed arrays are stored in a continuous chunk of memory, much like it does in array structures in C and C++ (which, by the way, is the language used to implement most, if not all JavaScript engines).

The major use case for typed arrays is, as hinted earlier, WebGL (which we'll cover in Chapter 6, Adding Features to Your Game). In WebGL, where we can perform 3D rendering right from JavaScript, we may need to process integer buffers, over a million elements long. These buffers can be used to represent a 3D model that we wish to draw to the screen. Now, imagine how long it would take for the browser to iterate through one such array. For each and every element, it would have to follow a memory location, check the value at that location, make sure the value is a number, attempt to convert the value into a number, and then finally use that value. Sounds like a lot of work? Well, that's because it is. With typed arrays, it can just run through that array as fast as it can, knowing that each element is indeed a number, and knowing exactly how much memory each element takes, so that jumping to the next memory address is a consistent and predictable process.

Typed arrays are also used in the 2D canvas context. As we'll see in the canvas API section later in the chapter, there is a way that we can get the pixel data from whatever is drawn into a canvas. All that this pixel data is, is a long array of 8bits clamped unsigned integers. What that means is that each element in this array can only be an integer value between 0 and 255, which is precisely what the acceptable values are for a pixel.

How to use it

Using typed arrays is really simple. It may be easier to understand how they work if you have at least some experience with C or C++. The easiest way to create a typed array is to declare our array variable, and assign it an instance of a particular typed array type.

var typedArr = new Int32Array(10);

In the example, we have created an instance of an integer array, where each element can be either positive or negative (signed). Each element will be stored as a 32 bits number. The integer argument that we pass in, indicates the size of the array. Once this array is created, its size cannot be changed. Any values assigned to it outside its bounds are silently ignored by the browser, as well as any illegal values.

Other than the restrictions on what can be stored in this special array, it all may seem just as an ordinary JavaScript array to the untrained eye. But if we look a bit deeper into it, we'll notice a couple more distinctions between an array and a typed array.

typedArr instanceof Int32Array; // True
typedArr.length == 10; // True

typedArr.push(23); // TypeError: <Int32Array> has no method 'push'
typedArr.pop(); // TypeError: <Int32Array> has no method 'pop'
typedArr.sort(); // TypeError; <Int32Array> has no method 'sort'

typedArr.buffer instanceof ArrayBuffer; // True
typedArr.buffer.byteLength == 40; //True

typedArr instanceof Array; // False

The first thing we notice is that the array is indeed an Int32Array, and not an Array. Next, we're happy to know that the length property is still there. So far so good. Then, things start to separate, as simple methods associated with regular arrays are no longer present. Not only that, but there's also a new attribute in the typed array object named buffer. This buffer object is of type ArrayBuffer, which has a byteLength property. In this case, we can see that the buffer's length is 40. It's easy to see where this 40 came from: buffer holds 10 elements (typedArr.length), and each element is 32 bits long (4 bytes), for a total of 40 bytes in the ArrayBuffer (hence the property name of byteLength).

Since typed arrays don't come with helper functions such as regular JavaScript arrays do, we read and write data to them using the old array notation, where we index into the array in order to read or write a value.

var typedArr = new Uint32Array(3);

typedArr[] = 0; // SyntaxError

typedArr[0] = 3;
typedArr[1] = 4;
typedArr[2] = 9;

for (var i = 0, len = typedArr.length; i < len; i++) {
   typedArr[i] >= 0; // True
}

Again, just to reinforce the fact that no helper functions or shortcuts related to ordinary JavaScript arrays work with typed arrays, notice that an attempt to access an element without providing an index will provide an exception being thrown by the browser.

ArrayBuffer and ArrayBufferView

Although, all the previous examples used a specific kind of typed array directly, the way that typed arrays work is slightly more involved than that. The implementation is broken down into two separate parts, namely, an array buffer and a view (or more specifically, an array buffer view). The array buffer is simply a chunk of memory that is allocated, so we can store our data there. The thing about this buffer is that it has no type associated with it, so we can't access that memory to store data to, or read data from it.

In order to be able to use the memory space allocated by the array buffer, we need a view. Although the base type for this view is ArrayBufferView, we actually need a subclass of ArrayBufferView, which defines a specific type to the data stored in the array buffer.

var buffer = new ArrayBuffer(32);
buffer.byteLengh == 32; // True

var i32View = new Int32Array(buffer);
i32View.length == 8; // True

Here's where things can get a bit confusing. The array buffer works in terms of bytes. As a refresher, a byte is made up of 8 bits. A bit is a single binary digit, which can have a value of either zero or one. This is how data is represented at its most basic format in computers.

Now, if a buffer works in terms of bytes, when we created our buffer in the example, we created a block of 32 bytes. The view that we create to hold and use the buffer can be one of nine possible types, each of which specifies a different data size (in terms of bits, not bytes). Thus, a view of type Int32 represents a buffer where each element is an integer, 32 bits long. In other words, a 32 bits view can hold exactly 8 bytes (1 byte = 8 bits; 32 bits = 8 bytes), as illustrated in following screenshot:

ArrayBuffer and ArrayBufferView

Array buffers work in terms of bytes. In the image, there are 4 bytes, although view types work in terms of bits. Thus, if we use a 32 bits view, it will result in an array that has a length of exactly one element. If the view uses a 16 bits data type, then the array will have 2 elements (4 bytes divided by 16 bits). Finally, if the view uses an 8 bits data type, the array stored in the 4 bytes buffer will have 4 elements.

Tip

One important thing to always remember is that when you create an array buffer, the length you choose to make the buffer must divide perfectly into however large you make the array buffer view. If there is not enough room in the buffer to fit entire bytes, the JavaScript will throw an error of type RangeError.

In the following image, the buffer is only big enough for 8 bits, all of which must be occupied by whole bytes. Thus, a view is an 8 bits number which would fit exactly one whole element, which would be fine. A 16 bits element would only fit half of an element, which is not possible. A 32 bits element would likewise only fit a portion of it, which is also not allowed.

ArrayBuffer and ArrayBufferView

As you can see, as long as the array buffer has a bit length that is a multiple of the bit size of the data type used in the view, things work out fine. If the view is 8 bits long, then an array buffer of 8, 16, 24, 32, or 40, would work out fine. If the view is 32 bits long, then the buffer must be at least 4 bytes long (32 bits), 8 bytes (64 bits), 24 bytes (96 bits), and so on. Then, by dividing the amount of bytes in the buffer by the amount of bytes in the data type represented by the view, we can calculate the total number of elements that we can fit in said array.

// 96 bytes in the buffer
var buffer = new ArrayBuffer(96);

// Each element in the buffer is 32 bits long, or 4 bytes
var view = new Int32Array(buffer);

// 96 / 4 = 24 elements in this typed array
view.length == 24;

Typed array view types

As a summary, a plain old array buffer has no actual size. Although it wouldn't make sense to create an array buffer of a byte length of say 5 bytes, we are more than welcome to do so. Only after the array buffer is created can we create a view to hold the buffer. Based on the byte size of the buffer, we can determine how many elements the array buffer view can access by selecting an appropriate data type. Currently, there are nine data types that we can choose from for the array buffer view.

  • Int8Array: It is a signed integer, 8 bits long, ranging from 32,768 to 32,767
  • Uint8Array: It is an unsigned integer, 8 bits long, ranging from 0 to 65,535
  • Uint8ClampedArray: It is an unsigned integer, 8 bits long, ranging from 0 to 255
  • Int16Array: It is a signed integer, 16 bits long, ranging from 2,147,483,648 to 2,147,483,647
  • Uint16Array: It is an unsigned integer, 16 bits long, ranging from 0 to 4,294,967,295
  • Int32Array: It is a signed integer, 32 bits long, ranging from 9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
  • Uint32Array: It is an unsigned integer, 32 bits long, ranging from 0 to 18,446,744,073,709,551,615
  • Float32Array: It is a signed float, 32 bits long, with a range of 3.4E +/- 38 (7 digits)
  • Float64Array: It is a signed float, 64 bits long, with a range of 1.7E +/- 308 (15 digits)

It goes without saying that the larger the view type, the larger the buffer will need to be to hold the data. Obviously, it follows that the larger the buffer you create, the more memory the browser will need to set aside for you, whether or not you end up using that memory. Thus, we should always pay attention to how much memory we might actually need, and try to allocate no more than that. It would be an awesome waste of resources to allocate an array of 10,000 elements, each of which are 64 bits long, just to represent a snake in a game, such as the one that we're building in the chapter, where the maximum snake size might be no larger than 50 or so elements, and where each element needs not hold a value larger than say 10.

Given such constraints, we could calculate a rough, yet optimistic array size of 50, where each element only needs 8 bits (since we'll only need around 10 unique values). Thus, 50 elements times one byte each, gives us a total buffer size of 50 bytes. This should be more than enough for our purposes, while the memory consumption for this buffer alone should stay around 0.05 KB. Not bad.

Finally, you may have noticed, the first part of this section demonstrated typed array creation without using the ArrayBuffer construct explicitly.

// Create a typed array with 4 elements, each 32 bits long
var i32viewA = new Int32Array(4);

// Create the same typed array, but using an explicit ArrayBuffer first
var buffer = new ArrayBuffer(16)
var i32viewB = new Int32Array(buffer)

While the two typed arrays above, refer to two separate and unique memory locations, they are identical at run time, and cannot be told apart (unless the actual arrays hold different values, of course); the point here being that an array buffer view constructor can take an ArrayBuffer, or simply an integer. If you use an ArrayBuffer, all of the restrictions just mentioned apply, and must be handled with care. If you only supply an integer, the browser will create an array buffer of the appropriate size for you automatically. In practice, there are rare occasions and reasons, where you'd want to manually create an array buffer separately. It is noteworthy, however, that it is totally legal to create multiple array buffer views for the same array buffer, even if each view is of a different data type. Remember that, since the buffer refers to a single memory location, so all views bound to the same buffer are sharing that memory space.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.128.105