Links to articles:
- Memory management
-
ArrayBuffers and SharedArrayBuffers
-
Use Atomics to avoid race conditions in SharedArrayBuffers
In the last article, I explained how automatic memory management languages like JavaScript work with memory, and I explained how manual memory management like C works.
Why spend so much time on memory management when we talk about ArrayBuffers and SharedArrayBuffers?
That’s because Arraybuffer gives you a way to process some data manually, even if you’re using JavaScript with automatic memory management.
Why did you do that?
As we discussed in our last article, automatic memory management is a double-edged sword that makes it easier for developers, but it adds some overhead. In some cases, this overhead can cause performance problems.
For example, when you create a variable in JS, the engine must guess what type of variable it is and how it should be represented in memory, and because of this guesswork, JS engines often reserve much more space for variables than they really need. Depending on the variable, there can be 2-8 times as many memory slots as needed, which can result in a lot of wasted memory.
In addition, certain patterns for creating and using JS objects can make garbage collection more difficult. If you are doing manual memory management, you have the flexibility to choose the memory allocation and garbage collection strategy required by the current program.
In most less performance-sensitive scenarios there is no need to worry about memory waste due to automatic memory management, and in many cases manual memory management may be slower.
But for those who need to work at the bottom to make code as fast as possible, Arraybuffer and SharedArrayBuffers give you an option.
How does an ArrayBuffer work?
Using an ArrayBuffer is just like using a JavaScript array, except that when using an ArrayBuffer, you can’t put any JavaScript types into it, such as objects or strings. The only things you can put into it are bytes (binary, It can be represented by the numbers 0 & 1.
Again, you don’t actually add this byte directly to the ArrayBuffer. As such, the ArrayBuffer doesn’t know how big a byte should be or how to convert different kinds of numbers into bytes.
An ArrayBuffer itself is just a combination of zeros and ones. The ArrayBuffer does not know where the distinction between the first and second elements in the array should be.
As mentioned in the last article, in order to break it down and put it in a box, we need to wrap it in what’s called a view. These data views can be added with typed arrays, and there are many different types of typed arrays available.
For example, you can take an array of type Int8 and split it into 8-bit bytes.
Or you can take an unsigned Int16 array, which will break it up into 16-bit bytes and treat it as if it were an unsigned integer.
You can even have multiple views on the same base buffer. Different views will give you different results for the same operation.
For example, if we get the element 0&1 from the Int8 view on this ArrayBuffer, it will give us a different value from the element 0 in the Uint16 view, even though they contain exactly the same bits.
In this way, an ArrayBuffer is basically like raw memory. It simulates languages such as C that operate directly on memory access.
You might wonder why you don’t just give the programmer access to memory, instead of adding this abstraction layer? The reason is that direct access to memory leads to some security vulnerabilities, which I’ll explain more about in a future article.
What is a shared buffer?
To explain SharedArrayBuffer, I need to talk a little bit about parallel and JavaScript running code.
You can run code in parallel to make it run faster, or to make it respond faster to user events. To do this, you need to break up the work.
In a typical application, the work is done by a single person — the main thread. I’ve talked about this before… The main thread is like a full-stack developer. It takes care of JavaScript, DOM, and layout.
Helping the main thread remove any work from the workload will help improve performance. In some cases, SharedArrayBuffer can reduce the amount of work that the main thread must do.
But sometimes reducing the workload on the main thread is not enough, and sometimes you need reinforcements… To break up the work.
In most programming languages, the usual way to break up work is to use threads, which is basically like having multiple people working on a project. If you have tasks that are very independent of each other, you can delegate them to different threads. The two threads can then work on their respective tasks simultaneously.
In JavaScript, the way to do this is to use something called a Web Worker. Web workers are slightly different from the threads you use in other languages in that they do not share memory by default.
This means that if you want to share some data with another thread, you have to copy it. This is done with the function PostMessage.
PostMessage takes the object you put into it, serializes it, and sends it to other Web workers, where it is deserialized and put into memory.
It’s a fairly slow process.
For some types of data, such as ArrayBuffers, you can perform what is called transfer memory. This means moving a specific chunk of memory onto it so that other Web workers can access it.
But the first Web worker can no longer access it.
This approach is fine in some scenarios, but in many scenarios where you want to have high performance parallelism, you really need to have shared memory — SharedArrayBuffers addresses this kind of problem
With SharedArrayBuffer, both Web workers and threads can write and read data from the same block of memory.
This means they don’t have the communication overhead and latency of postMessage. Both ends of the Web worker have immediate access to data.
However, direct access from two threads at the same time has some dangers. It leads to an unexpectedly state condition.
I’ll explain more in the next article.
How compatible is SharedArrayBuffers?
SharedArrayBuffers will soon be supported by all major browsers.
In Safari and support (in Safari 10.1), both Firefox and Chrome will be supported in July/August. Edge is supported in their fall Windows update. June 2017
Even if they are available in all major browsers, we don’t expect application developers to use them directly. In fact, we recommend against it, and you should use the highest level of abstraction available.
What we expect is that JavaScript library developers wrap this into libraries, giving you an easier and more secure way to use SharedArrayBuffers.
In addition, once SharedArrayBuffers are built into the platform, WebAssembly can use them to implement threading support. Once you’ve reached this point, you can use concurrency abstractions in languages like Rust, one of whose main goals is to use concurrency without fear.
In the next article, we’ll look at the tools (atoms) that the authors of these libraries will use to establish these abstractions while avoiding race conditions.
The original address: hacks.mozilla.org/2017/06/a-c…