原文:A cartoon intro to ArrayBuffers and SharedArrayBuffers
这是系列内三篇文章的第二篇:
- 内存管理
- ArrayBuffers 和 SharedArrayBuffers 的卡通介绍
- 使用 Atom 避免 ArrayBuffers 中的竞态条件
概述
In the last article, I explained how memory-managed languages like JavaScript work with memory. I also explained how manual memory management works in languages like C.
在上一篇文章中,我解释了像 JavaScript
这样的内存管理语言是如何工作的。我还解释了手动内存管理(比如 C
语言)如何工作。
Why is this important when we’re talking about ArrayBuffers and SharedArrayBuffers?
It’s because ArrayBuffers give you a way to handle some of your data manually, even though you’re working in JavaScript, which has automatic memory management.
为什么这很重要,当我们谈论 ArrayBuffers
和 SharedArrayBuffers
时?
因为 ArrayBuffers
为你提供了手动处理某些数据的方法,即使你使用的是自动内存管理的 JavaScript
。
Why is this something that you would want to do?
As we talked about in the last article, there’s a trade-off with automatic memory management. It is easier for the developer, but it adds some overhead. In some cases, this overhead can lead to performance problems.
为什么要这样做?
正如我们在上一篇文章中谈到的那样,自动内存管理有这样的选择:对于开发人员更容易使用,但增加了一些开销,而在某些情况下,这种开销可能会导致性能问题。
For example, when you create a variable in JS, the engine has to guess what kind of variable this is and how it should be represented in memory. Because it’s guessing, the JS engine will usually reserve more space than it really needs for a variable. Depending on the variable, the memory slot may be 2–8 times larger than it needs to be, which can lead to lots of wasted memory.
Additionally, certain patterns of creating and using JS objects can make it harder to collect garbage. If you’re doing manual memory management, you can choose an allocation and de-allocation strategy that’s right for the use case that you’re working on.
比如,当你在 JS
中创建变量时,JS
引擎必须猜测这是什么类型的变量,以及如何在内存中表示。这种猜测导致 JS
引擎通常会比为变量准备更多的空间。根据变量,内存占据的可能比需要大 2-8
倍,这会导致浪费大量的内存。
此外,创建和使用 JS
对象的某些模式,可能会使收集垃圾更难进行。如果你使用手动内存管理,则可以选择适用于你的情况的内存分配和释放策略。
Most of the time, this isn’t worth the trouble. Most use cases aren’t so performance sensitive that you need to worry about manual memory management. And for common use cases, manual memory management may even be slower.
But for those times when you need to work at a low-level to make your code as fast as possible, ArrayBuffers and SharedArrayBuffers give you an option.
大多数时候,这不是个麻烦。大多数情况下对性能的要求并不敏感到需要手动内存管理。甚至对于一些常见情况下,手动内存管理可能会更慢。
但是对于那些需要在底层工作以使代码尽可能快的时候,ArrayBuffers
和 SharedArrayBuffers
为你提供了另一个选择。
ArrayBuffer 如何工作呢?
It’s basically like working with any other JavaScript array. Except, when using an ArrayBuffer, you can’t put any JavaScript types into it, like objects or strings. The only thing that you can put into it are bytes (which you can represent using numbers).
ArrayBuffer
基本上就像使用任何其他 JavaScript
数组。除了使用 ArrayBuffer
时,你不能将任何 JavaScript
类型放入其中,例如对象或字符串。唯一可以放入的是二进制字节(可以使用数字表示)。
One thing I should make clear here is that you aren’t actually adding this byte directly to the ArrayBuffer. By itself, this ArrayBuffer doesn’t know how big the byte should be, or how different kinds of numbers should be converted to bytes.
The ArrayBuffer itself is just a bunch of zeros and ones all in a line. The ArrayBuffer doesn’t know where the division should be between the first element and the second element in this array.
有一点我需要明确指出,你并不能将这个字节直接添加到 ArrayBuffer
中。ArrayBuffer
本身不知道字节应该有多大,或者不同类型的数字如何转换成字节。
ArrayBuffer
本身就是一堆 0
和 1
全部放在一行。它并不知道这个数组中的第一个元素和第二个元素如何区分。
To provide context, to actually break this up into boxes, we need to wrap it in what’s called a view. These views on the data can be added with typed arrays, and there are lots of different kinds of typed arrays they can work with.
For example, you could have an Int8 typed array which would break this up into 8-bit bytes.
为了提供上下文,实际上将其分成多个盒子(注:就是把这一整行的 01 分割成多个分组),我们需要将它包装在视图中。这些数据视图可以添加为类型化的数组,并且有很多不同类型的类型数组可以使用。
例如,你可以使用 Int8
类型的数组,将其分解为 8
位一组。
Or you could have an unsigned Int16 array, which would break it up into 16-bit bites, and also handle this as if it were an unsigned integer.
或者你可以使用无符号的 Int16
数组,它可以将其分解成 16
位一组,这样即使是无符号整数也可以处理。
You can even have multiple views on the same base buffer. Different views will give you different results for the same operations.
甚至可以在同一个基本 buffer
上拥有多个视图。在不同的仕途上执行相同的操作结果也不同。
For example, if we get elements 0 & 1 from the Int8 view on this ArrayBuffer, it will give us different values than element 0 in the Uint16 view, even though they contain exactly the same bits.
例如,如果我们从这个 ArrayBuffer
的 Int8
视图可以获取元素 0
和 1
,那么,在 Uint16
视图下,元素 0
将会返回不同的值,即使它们拥有完全相同的 bits。
In this way, the ArrayBuffer basically acts like raw memory. It emulates the kind of direct memory access that you would have in a language like C.
You may be wondering why don’t we just give programmers direct access to memory instead of adding this layer of abstraction. Giving direct access to memory would open up some security holes. I will explain more about this in a future article.
这样,ArrayBuffer
基本上类似于原始内存,它可以模拟像 C
语言的直接的内存访问。
你可能会想,为什么我们不让程序员直接访问内存,而是添加这一层抽象。这是因为直接访问内存将会造成一些安全漏洞。我将在以后的文章对此解释更多。
什么是 SharedArrayBuffer?
To explain SharedArrayBuffers, I need to explain a little bit about running code in parallel and JavaScript.
要介绍 SharedArrayBuffers
,我需要介绍一下并行运行代码和 JavaScript
。
You would run code in parallel to make your code run faster, or to make it respond faster to user events. To do this, you need to split up the work.
In a typical app, the work is all taken care of by a single individual—the main thread. I’ve talked about this before… the main thread is like a full-stack developer. It’s in charge of JavaScript, the DOM, and layout.
Anything you can do to remove work from the main thread’s workload helps. And under certain circumstances, ArrayBuffers can reduce the amount of work that the main thread has to do.
并行运行代码会使代码运行速度更快,或者使其对用户事件的响应更快。要做到这一点,你需要分离工作。
在典型的应用程序中,所有的工作都由单个人: 主线程来处理。主线程就像一个全栈开发者,它负责 JavaScript
,DOM
和布局。
从主线程的工作负载中删除任何工作都会有帮助,在某些情况下,ArrayBuffers 可以减少主线程所需的工作量。
But there are times when reducing the main thread’s workload isn’t enough. Sometimes you need to bring in reinforcements… you need to split up the work.
但有时减少主线程的工作量是不够的。有时你需要引进援助,你需要分离工作。
In most programming languages, the way you usually split up the work is by using something called a thread. This is basically like having multiple people working on a project. If you have tasks that are pretty independent of each other, you can give them to different threads. Then, both those threads can be working on their separate tasks at the same time.
In JavaScript, the way you do this is using something called a web worker. These web workers are slightly different than the threads you use in other languages. By default they don’t share memory.
在大多数编程语言中,分割工作的方式是使用线程,这就像有多个人在一个项目上工作。如果你有相互独立的任务,你可以分配给不同的线程。然后,这些线程就可以同时处理独立的任务。
在 JavaScript
中,你可以使用 web workers
做这些事。web workers
与其他语言中使用的线程略有不同,默认情况下,它们不共享内存。
This means if you want to share some data with the other thread, you have to copy it over. This is done with the function postMessage.
postMessage takes whatever object you put into it, serializes it, sends it over to the other web worker, where it’s deserialized and put in memory.
这意味着如果要与其他线程共享数据,则必须复制,这是通过 postMessage
函数完成的。
postMessage
会把你放入的任何对象序列化,发送到其他 web worker
,然后其他 web worker
反序列化并放入内存中。
That’s a pretty slow process.
For some kinds of data, like ArrayBuffers, you can do what is called transferring memory. That means moving that specific block of memory over so that the other web worker has access to it.
But then the first web worker doesn’t have access to it anymore.
这是一个很慢的过程。
对于一些类型的数据,像 ArrayBuffers
,你可以移动内存,这意味着移动该内存块,以便其他 web worker
可以访问它,但是第一个 web worker
将不能访问它了。
That works for some use cases, but for many use cases where you want to have this kind of high performance parallelism, what you really need is to have shared memory.
This is what SharedArrayBuffers give you.
这适用于一些情况,但是对于想要具有高性能的并行,你真正需要的是共享内存。这正是 SharedArrayBuffers
可以提供的。
With the SharedArrayBuffer, both web workers, both threads, can be writing data and reading data from the same chunk of memory.
This means they don’t have the communication overhead and delays that you would have with postMessage. Both web workers have immediate access to the data.
There is some danger in having this immediate access from both threads at the same time though. It can cause what are called race conditions.
使用 SharedArrayBuffer
,这些 web workers
,线程都可以从同一块内存中写入数据和读取数据。这意味着不再有 PostMessage
通信的开销和延迟, web workers
可以立即访问数据。
这两个线程的同时访问内存会有一些危险,它可能引起竞争条件的问题。
I’ll explain more about those in the next article.
我将会在下一篇文章解释这个问题。
SharedArrayBuffers 现状怎样?
SharedArrayBuffers will be in all of the major browsers soon.
SharedArrayBuffers
即将在所有主流浏览器中。
They’ve already shipped in Safari (in Safari 10.1). Both Firefox and Chrome will be shipping them in their July/August releases. And Edge plans to ship them in their fall Windows update.
它们已经在 Safari
中运行(Safari 10.1
中)。 Firefox
和 Chrome
都将在7月、8月发行。而 Edge
计划在他们秋天的 Windows
更新中发布。
Even once they are available in all major browsers, we don’t expect application developers to be using them directly. In fact, we recommend against it. You should be using the highest level of abstraction available to you.
What we do expect is that JavaScript library developers will create libraries that give you easier and safer ways to work with SharedArrayBuffers.
In addition, once SharedArrayBuffers are built into the platform, WebAssembly can use them to implement support for threads. Once that’s in place, you’d be able to use the concurrency abstractions of a language like Rust, which has fearless concurrency as one of its main goals.
In the next article, we’ll look at the tools (Atomics) that these library authors would use to build up these abstractions while avoiding race conditions.
即使在所有主流浏览器都可用,我们也不希望应用程序开发人员直接使用它们。实际上,我们建议不要直接使用,应当使用它们的最高级别的抽象封装库。
我们希望 JavaScript
库的开发人员创建可以使你可以更轻松,更安全地使用 SharedArrayBuffers
的库。
在下一篇文章中,我们将介绍这些库作者建立的避免竞争条件的抽象的工具(Atomics
)。