Sunday, April 23, 2017

WebAssembly - sending a Javascript array to wasm for modification and getting back the modified version.



Alright so WebAssembly has hit the MVP and is enabled by default in more than one browser right now, so starting to use it makes sense.

One of the main functions of WebAssembly is to let it handle the heavy lifting part of the code.

This post is going to take you through sending an array to a 'wasm' function, make some crazy low level stuff on its elements and finally reflecting that change on the web page with Javascript.

Note that this post explains how to send an array that's already filled, which involves copying the elements into the WebAssembly memory space. In a lot of cases, though, you would want to skip the copying part and just build the array incrementally directly into the WebAssembly memory space (see the JS code near the end).

For the sake of simplicity, his post assumes that you are using C/C++ with Emscripten to generate the helper JS file.

C

Let's start at the C level (sorry)

#include <stdint.h>

int addThree(uint8_t *buf, int len) {
  uint8_t *item;
  uint8_t *end = buf + len;

  for (item = buf; item<end; item++) {
    *item += 3;
  }

  return 0;
}

Let's break things down..

On line 1, we include the stdint library which gives consistent operation across different machines.

On line 3, we define our function, it takes two parameters:

  • a pointer to the start address of the array in memory (which is technically the location of index 0). Array pointers have to be one byte in size.
  • and the number of elements in this array (to help us loop through the items).

We then define two pointers to the start and end location of the array in memory (it is safe to add together diffreent integer sizes).

We then loop through the array items and do the crazy stuff (like adding 3 to each element in this case) and just return 0 to say that we are done.

Look out for overflows, like in the above example, we have used the unsigned int8 integer to hold the data, which can store integers from 0 to 255, so adding a 3 to say a 253 will cause an overflow and get you into trouble.

Alright, One thing left, we've got to compile that code and export the addThree function.

assuming that you already have emcc into your PATH, compile the C file with this (I choose the name "freeThree.c", because it rhymes).

 emcc -o three.html freeThree.c -s WASM=1 -s EXPORTED_FUNCTIONS="['_addThree']"

Here we are just telling the compiler to use native WebAssembly (and not asm.js) and we tell it to expose the addThree function.

Notice the "_" before the function name, this is very important.


While we are here and before we go to the Javascript side of things, if you may have to pass large arrays to this function, consider making room for the extra memory.

one technique that I use is to use the ALLOW_MEMORY_GROWTH flag, run the web page, and keep an eye on the console, it's going to give you the a number every time is grows the memory. Note that when your app demands extra memory, the size of memory doubles rather than keping up with the exact demanded size, still you can get a nice approximation about what your maximum memory size should be. Add this to the end of the previous compile command after EXPORTED_FUNCTIONS="['_addThree']".

-s ALLOW_MEMORY_GROWTH=1

ALLOWMEMORYGROWTH prevents some optimizations that the compiler can make, so after you have decided that your app will use a maximum of X memory it's better to use that number for specifying the static memory of your app. So instead of the ALLOW_MEMORY_GROWTH flag, use this

-s TOTAL_MEMORY=X


Javascript

Now let's shift our focus to the Javascript level.

After you've run the above command, emscripten should have outputted a nice bloated three.html file with a .js file with the same name (along with the .wasm file itself), this .js file is gonna make our life a lot easier when dealing with the weird WebAssembly memory model.

We won't change anything in the script file, it's just our interface to the WebAssembly binary (.wasm file), it fetches, compiles and instantiate the wasm file and gives us a nice and clean Module object ready to be used.

Now open the three.html file and inside the first <script> tag, right before the closing </script> tag which is right above the other <script> tag (I hope this has clarified more than confused..), create a function with the following Javascript (you can use it as an event handler, or just shove it into the Mosule's postRun array, which is on around line 1226 in your three.html file, to run it as soon as the Module loads)

const myArray = new Uint8Array([100, 200, 250]);

const buffer = Module._malloc(myArray.length);
Module.HEAPU8.set(myArray, buffer);

Module.ccall('addThree', 'number', ['number', 'number'], [buffer, myArray.length]);

console.log(Module.HEAPU8.subarray(buffer, buffer+myArray.length));

Module._free(buffer);

Here's what's going on..

On line 1, we create our array that we wish to send to the C function for manipulation.

Please note that AFAIK (and according to the emscripten docs), you can only send Uint8Array or or Int8Array. If you have figured out a way to send other typed arrays please share it with us.

Then on line 3, we allocate enough memory (in bytes, it's just the length of the array here because each element is one byte in size) to hold our array elements.

We then actually copy the elements from the array into the buffer we have just created, which means that we are copying the data into the WebAssembly Module's memory space. While the module's memory is represented as a single ArrayBuffer, Module.HEAPU8 is the Uint8 view of that buffer, so it's just a normal Javascript typed array built around that buffer, so in turn, it has the set function.

After that we use the utility function ccall to call the C function, passing in the buffer to act as a pointer to the first element in the array.

ccall docs and other options of calling the C function can be found here.

Line 8 uses the subarray function to peek at the array items while they are sitting happily in their ArrayBuffer, you can use subarray or slice to deal with the modified elements. You can find more details here, but the bottom line is if you just wanna look at the elements or loop over them or the likes and then throw them, use subarray, it's way faster then slice.

Finally we must free up the allocated memory space (a la C malloc/free functions).


The catch

Copying the data (with set) takes time, if you have a huge array this can easily be your app's performance bottleneck.

Imagine you have a canvas that you want to manipulate which is 4000 in width and 3000 in height, this canvas has 4000*3000 pixels, which is 12 million pixels. Now you want to get the ImageData array to be processed by the fast C function, this array gives every pixel 4 entries, which is 4 bytes, now we have 48 million entries/bytes that we have to copy into the memory space of WebAssembly. On a modern machine this takes something close to 2 seconds!

So three's a tradeoff happening here, just be sure that copying the data isn't going to be your bottleneck (like, don't use it for adding 3 to the entries). If so, you better off doing it with Javascript, it would be a lot faster (You can also use workers to get some concurrency).


Hope that helps clear things up. please share your suggestions and questions below.

Thursday, April 13, 2017

Javascript typed arrays - slice vs subarray



What

Both of these TypedArrays' methods appear to do the same thing, except for a cool difference. Let's start with examples..

const a = new Uint8Array([1, 2, 3, 4]);

const b = a.slice(1, 3); // b becomes Uint8Array [ 2, 3 ]
const c = a.subarray(1, 3); // c becomes Uint8Array [ 2, 3 ]

Now let's see what happens when we change the values of these new arrays

b[0] = 5; // b is now Uint8Array [ 5, 3 ]
console.log(a); // Uint8Array [ 1, 2, 3, 4 ]

c[0] = 5; // c is now Uint8Array [ 5, 3 ]
console.log(a); // Uint8Array [ 1, 5, 3, 4 ]

See what happened at line 5? when we changed c, a has changed too, where on the other hand, b doesn't have the same effect, that's the difference.


Why

As you know, subarray exists only for typed arrays, that's the key to understand what's going on.

TypedArrays in javascript are represented in the memory as a single slab of bytes with the size reflecting the number of array items (if every item is one byte and the array has 3 items, then the memory taken is 3 bytes). Let's compare what slice does with what subarray does..

  • slice: Allocate a new space in memory and copy the items from the original array between the start and end indices provided in the arguments into these new memory locations, return the new array.
  • subarray: make a new empty typed array object, which points to a part of the original array between the start and end indices provided in the arguments.

So basically subarray lets a and c see and modify the same memory locations, it provides a smaller "view" (window) over the original array.

That makes subarray very fast compared to slice, because no copying is involved.


When

you can take advantage of this when you have a huge array that you want to convert a range of it into another type. One such situation that comes to mind is when the canvas demands a Uint8ClampedArray to draw its pixels.

const d = new Uint8ClampedArray(c);

the constructor sees c as a Uint8Array, because it really is an independent array which happens to look at the same memory location as a.

This gives you a new array d with the contents of a part of a without having to create an intermediate copy of the range that we want from a.


I hope I have managed to clear up the difference. feel free to ask questions, point out flaws and speak your mind in the comments.