在《内存管理技术的具体实现介绍》一文中,我解释了像JavaScript这样的内存管理语言是如何工作的,我还解释了手动内存管理如何在C语言中工作。
本文就让我谈论ArrayBuffers和SharedArrayBuffers。这是因为即使你正在使用具有自动内存管理的JavaScript,ArrayBuffers也可以为你提供一种方法来手动处理一些数据。
正如大家在上一篇文章中了解的那样,出于对自动内存管理安全性的考虑,开发人员更容易使用它,但也会增加一些开销。但在某些情况下,这种自动内存管理可能会导致性能问题。
例如,当你在JS中创建变量时,引擎必须判断这是什么样的变量,以及如何在内存中进行表示。因为引擎是处于判断的状态,所以JS引擎通常会需要的空间比实际的处理变量的空间更多一些。根据变量的不同,内存插槽可能比需要大2-8倍,这可能会导致大量内存的浪费。
另外,创建和使用JS对象的某些模式可能会使得收集垃圾变得困难。如果你正在进行手动内存管理,则可以选择适用于你正在使用的分配比例甚至可以取消磨人的分配策略。
不过大多数时候,由于手动内存管理比较麻烦,很多人会选择自动内存管理。另外,大多数用户对性能不敏感,所以总体来说,手动内存管理的使用不太多。对于常见的一些情况,手动内存管理甚至可能会让性能更慢。
但是对于那些需要在低级别状态下运行并希望代码尽可能快的用户来说,ArrayBuffers和SharedArrayBuffers则是一个比较好的选择。
ArrayBuffer的工作原理
它基本上就像使用任何其他JavaScript数组一样,除了使用ArrayBuffer之外,你不能将任何JavaScript类型放入其中,例如对象或字符串。唯一可以放入的是字节,可以使用数字表示。
有一点我应该在这里明确指出,你并需要将这个字节直接添加到ArrayBuffer中。因为ArrayBuffer本身并不知道字节应该有多大,或者不同的数字应该如何转换成字节。
ArrayBuffer本身就是一行一堆0和1组成的序列, ArrayBuffer不知道这个数组中第一个元素和第二个元素之间如何分割。
为了提供上下文,实际上会将其分解成各个小框,我们需要将它包装在所谓的视图中。这些数据视图可以添加类型化的数组,并且可以使用许多不同类型的类型数组。
例如,你可以使用Int8类型的数组,将其分解为8位字节。
或者你可以有一个无符号的Int16数组,它可以将其分解成16位,处理它就好像是处理一个无符号的整数。
你甚至可以在同一个基本缓冲区上拥有多个视图。不同的视图将给你呈现相同的操作下的不同结果。
例如,如果我们从这个ArrayBuffer的Int8视图获取元素0和1,那么它将给出与Uint16视图中的元素0不同的值,即使它们包含完全相同的位。
这样,ArrayBuffer基本上就类似于原始内存,你会用C语言模拟一种直接的内存访问,
你可能会想知道为什么我们不让程序员直接访问内存,而是添加这一层抽象化的内存。直接访问内存将会打开一些安全漏洞。。
SharedArrayBuffer的工作原理
在介绍SharedArrayBuffers之前,我需要解释一下并行运行代码和JavaScript。
使用并行运行代码,可以让你的代码运行速度更快,或者使其对用户事件的响应更快。要做到这一点,你需要分别独立运行。
在一个典型的应用程序中,所有的工作都由主线程来处理。我之前已经谈过了这个,主线程就像一个全栈开发者。它负责JavaScript,DOM和布局。
所以删除主线程的工作负载会对运行速度有很大的帮助。在某些情况下,ArrayBuffers可以减少主线程所需的工作量。
但有时减少主线程的工作量是不够的,有时你需要对要运行的工作分解,逐个击破。
在大多数编程语言中,通常分割工作的方式是使用一种叫做线程的东西,就像有多个人在一个项目上工作。如果你有相互独立的任务,你可以给他们不同的线程。然后,这两个线程可以同时处理单独的任务。
在JavaScript中,你的方式是使用名为web worker的东西。这些web worker与你在其他语言中使用的线程略有不同。默认情况下,它们不共享内存。
这意味着如果要与其他线程共享一些数据,则必须将其复制。这是通过postMessage函数完成的。
postMessage会把你放入的任何对象序列化,然后发送到另一个web worker,反序列化并放入内存中,这是一个相当缓慢的过程。
对于某些类型的数据,像ArrayBuffers,你可以做所谓的传输内存。这意味着对特定的内存块进行移动,以便其他web worker可以访问它。
但是,第一个web worker不再可以访问它了。
这个方法仅适用于一些特殊情况,想要具有高性能的并行性,真正需要的是具有共享内存,这就要用到SharedArrayBuffers。
使用SharedArrayBuffer,这两个线程都可以从同一块内存中写入数据和读取数据。
这意味着它们不需要使用PostMessage的通信开销和延迟,web worker就可以立即访问数据。
尽管这两个线程的同时立即访问有一些危险,但这样却可以引起所谓的竞争条件即两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序。
SharedArrayBuffers的当前用途
SharedArrayBuffers即将在所有主流浏览器中被用到。
目前它们已经在Safari中开始运行(在Safari 10.1中)。 Firefox和Chrome都将在今年的7月和8月发行它们,而Edge计划在今年秋天的Windows更新中用到。
即使在所有主流浏览器都可用,我们也不希望应用程序开发人员直接使用它们。其实我的建议是应该使用最高级别的抽象。
我期望的是,JavaScript库开发人员将创建库,使你可以更轻松,更安全地使用SharedArrayBuffers。
此外,一旦SharedArrayBuffers内置到平台中,WebAssembly可以使用它们来实现对线程的支持。一旦该功能实现,你将能够使用一种语言的并发抽象,如Rust语言, 其神奇的并发模型,能将安全和并发完美地统一在一起。
在下一篇文章中,我将介绍这些库的开发者如何在避免竞争的条件下同时建立这些抽象的工具(Atomics)。