To compare the performance between JS and WebAssembly, we use an example that calculates the collisions between circles on the screen. This quickly slows down the rendering performance making the performance comparison visible. We briefly go over the same steps of converting JS into C code, and find that the JS version of the code is actually faster than the WASM version - WebAssembly improving performance is not such a simple argument.
Demo repo: https://github.com/guybedford/wasm-demo
Note the demo is currently only supported in Chrome Canary with the Experimental Web Platform flag (chrome://flags/#enable-experimental-web-platform-feature) enabled due to the use of ES modules and WebGL 2.0.
[00:00] In this example, we're calculating the collisions between the circles on the page, which adds quite a performance overhead. If I increase the circle_count to just 2,000, we can already start to see a significant slowdown, so let's see if our conversation to WebAssembly can make this faster.
[00:14] Without going into the details of the collision algorithm, we're just going to copy the JavaScript into our C file to begin the conversion. To go through the steps briefly, define circle_count as a macro, update the function signatures, add types to our variables, fix up the iteration to find our external maths functions, and update their calls, add includes for the math and [indecipherable] libraries.
[00:40] Our use of square root, power and [indecipherable] can then be in-lined by the compiler by using these functions.
[00:46] Next, define our circle and circle velocity structs and initialize arrays of these -- of the length, circle count -- for their static allocation [indecipherable] get circle_count function and to get circle_data_offset function to know where to find the address of the circle position data in wasm memory.
[01:03] Finally, we fix up the index access within our circle_data struct arrays to correctly reference the data values. We can then compile using WasmFiddle, downloading the final WebAssembly binary file. In a new page, I can now include the wasm helper and use that to load the WebAssembly binary, which I've located at dynamics_call.wasm.
[01:26] The second argument of this function is the imports. We need to set the environment, which contains a random F and the exp function.
[01:35] The promise [indecipherable] us back the instantiated module, and we can then read off the circle_count and circle_data_offset from the functions that we imported. We can construct our typed array data for the circle data, using the offset that we've been provided, as well as the fact that we know that it's going to be three times the circle_count in length in the WebAssembly memory.
[02:00] Finally, we can render that circle data, passing the init and timestep methods from our WebAssembly binary, into the render function. Putting the JS simulation on the left and the WebAssembly version on the right, we can compare the performance directly.
[02:17] The unfortunate truth is that the JS version, on the left, actually ends up still faster than our WebAssembly. WebAssembly isn't a silver bullet for performance. It may just be that we're in the early days of optimization for WebAssembly in browsers, but we can get past this today if we approach performance with the right principles.