JS 翻译|深入 JavaScript 数组的演变和性能

前言

此文翻译自 Diving deep into JavaScript array – evolution & performance

名词解释

定型数组:ArrayBuff 参照 《JavaScript 高级程序设计》

同构数组:元素的数据类型全相同的数组

译者注:对象数组不参与讨论,相同数据结构的对象数组读写耗时不会缩短

文章

Before starting the article I would like to state that this is not about the basic of JavaScript array. Neither about teaching syntaxes or showing usage examples. The article is more about memory representation, optimization, behavior differences over syntaxes, performance and the recent evolution.

在文章开始之前,我想说一下,这篇文章与 JavaScript 数组的基础无关。既不教授语法也不展示使用例子。这篇文章更多的涉及内存表示,优化,语法上的行为差异,性能和最新发展

When I started using JavaScript for the first time; I was already familiar with C, C++, C# etc. And trust me like many other C/C++ people, I also didn’t have a good first date with JavaScript.

当我第一次开始使用 JavaScript 的时候,我已经非常熟悉 C、C++、C# 等。相信我,就像许多其他 C/C++ 的开发者一样,开始使用的时候,我也非常的不习惯

One of the major reasons why I didn’t like JavaScript was, its Array. As JavaScript arrays are implemented as hash-maps or dictionaries and not contiguous, I was feeling like this is a B grade language; can’t even implement an array properly. But since then both JavaScript and my understanding with JavaScript has changed… a lot.

我不喜欢 JavaScript 的一个主要原因是它的 Array,因为 JavaScript 数组是基于哈希或者字典而不是连续存储空间实现的,所以我感觉它是一种 B 级语言。甚至不能正确的实现一个数组。但是从那次之后,我对 JavaScript 的理解发生了很大的变化

Why JavaScript arrays are not actual arrays

为什么 JavaScript 数组不是实际数组

Before stating something about JavaScript, let me tell you what is an Array.

在开始介绍 JavaScript 之前,让我先告诉你什么是数组

So, arrays are a bunch of continuous memory location, used to hold some values. Here the emphasis is on the word continuous or contiguous; because this has a significant effect.

其实,数组就是一堆连续的存储单元,用来保存一些值,这里强调一下“连续”或“连续的”,因为这有很大的作用

image

An memory representation of an array has been provided in the picture above. So it is made to hold 4 elements of 4 bits each. Thus it is taking 16 bits memory blocks, all in the same order.

在上图中提供了一个数组的内存表示法,从图可知,它可以容纳 4 个元素,每个元素有 4 位。因此,它以相同的顺序获取 16 位的内存块

Suppose, I’ve declared tinyInt arr[4]; and it has captured a series of memory blocks, starting from address 1201. Now at some point if I try to read a[2], what it will do is a simple mathematical calculation to find out the address of a[2]. Something like 1201 + (2 X 4) and will directly read from address 1209.

假设,我声明了 tinyInt arr[4] 然后它已经获取了地址从 1201 开始的一系列内存块。在某个时候,我尝试读取 arr[2],那么它会通过做一个简单的数学计算来找到 arr[2] 的地址。比如 1201 + (2 * 4) ,然后直接从地址 1209 开始读取

image

In JavaScript, an array is a hash-map. It can be implemented using various data structures, one of them is linked-list. So in JavaScript if you declare an array var arr = new Array(4); it will make a structure like the picture above. Thus, if you want to read from a[2] any any point in your program; it has to traverse starting from 1201to find the address of a[2].

在 JavaScript 中,数组是一个哈希映射。它可以通过各种数据结构来实现,其中之一就是链表。所以在 JavaScript 中如果你声明一个数组 var arr = new Array(4);它将形成上图所示的结构。因此如果你想从程序中的任何点读取数组元素 a[2],它必须从地址 1201 开始遍历查找 a[2] 的地址

So this is how JavaScript arrays are different from actual arrays. Obviously a mathematical calculation will take way lesser time than an linked-list traversal. For long arrays, life will be more tough.

所以这就是 JavaScript 数组与实际数组的不同之处。显然数学计算比链表遍历花费的时间更少。对于大数组,将会更明显

Evolution of JavaScript arrays

JavaScript 数组的演变

Remember the days when we used to feel jealous if a friend gets 256MB RAM in his computer? But today, 8GB RAM is a common thing.

还记得当初我们嫉妒朋友的计算机拥有 256MB RAM 的日子吗?但是今天 8GB RAM 已经很普遍了。

Just like this, JavaScript has a language also have evolved a lot. With the immense effort from V8, SpiderMonkey, TC39 and the growing number of web users, JavaScript has become a necessity for the world. And performance improvement is an obvious need if you have a huge user base.

像这个一样,JavaScript 作为一门语言也发展了很多。随着 V8、SpiderMonkey、TC39 的巨大努力以及不断增长的 web 用户,JavaScript 已经成为世界的必须品了。如果你拥有庞大的用户基础,性能提升将是一个明显的需求

JavaScript engines these days actually allocate contiguous memory for its arrays; if the array if homogeneous (all elements of same type). Good programmers always keep their array homogeneous and JIT (just in time compiler) taking the advantage of that does all its array reading calculation just like the way C compiler does.But, the moment you want to insert an element of different type on that homogeneous array, JIT de-structure the entire array and recreate with the old-days style.So, if you are not writing bad codes, JavaScript Array objects maintains an actual array behind the scene, which is really great for the modern JS developers.

如今,如果是同构数组(所有的元素类型相同),JavaScript 引擎实际上也会为它们的数组分配连续的内存。优秀的开发者会始终保持它们数组的同构性,然后 JIT(即时编译器)利用这一点,像 C 编译器那样进行数组的所有计算。但是,当你想在同构数组中插入不同类型的元素时,JIT 会对整个数组进行解构,然后用旧的方式重新创建。所以如果你不写垃圾代码,JavaScript 数组对象在幕后就是一个实际数组。这非常有利于现代的 JS 开发者

Not only that, the arrays have evolved even more with ES2015 or ES6. TC39 decided to include typed array in JavaScript and thus; we have ArrayBuffer with us today.

不仅如此,随着 ES2015(ES6) 的出现,数组的发展更加迅猛。TC39 决定在 JavaScript 中包含定型数组,我们如今能在 JS 中使用 ArrayBuffer

ArrayBuffer gives you a chunk of contiguous memory chunk and let you do whatever you want with it. However, dealing directly with memory is very low level and more complicated. So we have Views to deal with ArrayBuffer. There are already some views available and more can be added in future.

ArrayBuff 给你一个连续的内存快,让你做任何你想做的事情。然而,直接处理内存是非常低级的,而且更加复杂。所以我们可以用 Views 处理 ArrayBuff 。现在已经有一些可用的 Views 了,后面还会逐步添加更多。

var buffer = new ArrayBuffer(8);
var view   = new Int32Array(buffer);
view[0] = 100;
复制代码

If you want to know more about the usage of Typed Arrays in JavaScript, you can go through the MDN Documentation.

如果你想了解更多 JavaScript 中关于定型数组的知识,你可以查阅 《MDN 文档》中文版

Typed arrays are performant and efficient. It was introduced after the request from WebGL people, as they were facing immense performance issues without a way to handle binary data efficiently. You can also share the memory using SharedArrayBuffer across multiple web-workers to get a performance boost.

定型数组是高性能并且高效的,它是在 WebGL 用户提出请求后引入的,因为在处理二进制数据的情况下面临着巨大的性能问题。你也可以通过使用 SharedArrayBuffer 在多个 web-worker 之间共享内存,从而实现性能优化

Amazing right? It started from simple hash-maps and now we are dealing with SharedArrayBuffer.

很神奇对吧?数组的讨论从简单的哈希映射开始,现在我们在处理 SharedArrayBuffer

Old Array vs Typed Array – performance

原来的数组 VS 定型数组 – 性能方面

We’ve talked a lot on array evolution in JavaScript. Now let’s check how beneficial are the modern arrays. I have done some small tests here and all are done with Node.js 8.4.0 on Mac.

我们聊了很多 JavaScript 中数组的发展,现在让我们来看看现代数组的好处。下面是我用 Mac 电脑在 Node.js 8.4.0 中做的一些实验

此处译者用 Chrome 90.0.4430.93 重新实验

Old Array – insertion

旧数组 – 插入

var LIMIT = 10000000;
var arr = new Array(LIMIT);
console.time("Array insertion time");
for (var i = 0; i < LIMIT; i++) {
    arr[i] = i;
}
console.timeEnd("Array insertion time");
复制代码

Time taken: *55ms*

译者耗时:21.2841796875 ms

Typed Array – insertion

var LIMIT = 10000000;
var buffer = new ArrayBuffer(LIMIT * 4);
var arr = new Int32Array(buffer);
console.time("ArrayBuffer insertion time");
for (var i = 0; i < LIMIT; i++) {
    arr[i] = i;
}
console.timeEnd("ArrayBuffer insertion time");
复制代码

**
**

Time taken: *52ms*

译者耗时:45.9052734375 ms


***

Damn, what am I seeing? The performance of old traditional array and ArrayBuffer are same? Nope. Remember, I told before that the compilers these days are smart and converts the traditional array to an actual contiguous memory array internally, if it’s homogeneous. That’s exactly what happened here in the first example. Though I used new Array(LIMIT), then also it was maintaining a modern array in it.

Now let’s modify the first example and make it a heterogeneous array and let’s se if there’s any performance difference.

该死,我看到了什么?传统的数组和 ArrayBuffer 的性能是一样的吗?不,记住,我之前说过,现在的编译器很聪明,对于同构数组,它会在底层使用连续的内存数组来处理。这就是第一个例子中发生的事情,虽然我使用的新数组,但它也在其中维护了一个现代数组。现在让我们修改第一个例子,把它变成一个异构数组,看看是否有任何性能差异

译者注:ArrayBuffer 中耗时比 Array 中更长的原因,可能是 ArrayBuffer 中还要维护 Views 视图

Old Array – insertion (heterogeneous)

旧数组-插入(异构)

var LIMIT = 10000000;
var arr = new Array(LIMIT);
arr.push({a: 22});
console.time("Array insertion time");
for (var i = 0; i < LIMIT; i++) {
    arr[i] = i;
}
console.timeEnd("Array insertion time");
复制代码

Time taken: *1207ms*
*
*


***

译者耗时:628.026123046875 ms

All I have done here is, added a new expression in line no. 3 to make the array heterogeneous. Everything else is exactly same as before. But see the performance difference. It’s 22 times slower.

我这里只是先往数组中插入了一个对象,使得数组达到异构的效果,此处也可以将 arr.push({ a: 22 }) 改成 arr.push("hello world") ,让实验排除引用对象的干扰。它比原来慢了超级多

Old Array – read

旧数组 – 读取

var LIMIT = 10000000;
var arr = new Array(LIMIT);
arr.push({a: 22});
for (var i = 0; i < LIMIT; i++) {
    arr[i] = i;
}
var p;
console.time("Array read time");
for (var i = 0; i < LIMIT; i++) {
    //arr[i] = i;
    p = arr[i];
}
console.timeEnd("Array read time");
复制代码

Time taken: *196ms*
*
*

译者耗时:18.998779296875 ms

没有太大的区别,可能因为 hash 索引优化的缘故

Typed Array – read

定型数组 – 读取

var LIMIT = 10000000;
var buffer = new ArrayBuffer(LIMIT * 4);
var arr = new Int32Array(buffer);
console.time("ArrayBuffer insertion time");
for (var i = 0; i < LIMIT; i++) {
    arr[i] = i;
}
console.time("ArrayBuffer read time");
for (var i = 0; i < LIMIT; i++) {
    var p = arr[i];
}
console.timeEnd("ArrayBuffer read time");
复制代码

Time taken: *27ms*


***

译者耗时:23.042236328125 ms

Conclusion

结论

Introduction of typed array in JavaScript is a great step. Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array etc are typed array views, who are in native byte order. However, you can also check DataView to create your custom view window. Hope in future we will find more DataView libraries to use ArrayBuffer with ease.

在JavaScript中引入类型化数组是非常优秀的操作。Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array等是定型数组的视图,它们是按本地字节顺序排列的。但是,您也可以检查 DataView 来创建自定义视图窗口。希望将来我们能找到更多的 DataView 库来方便地使用 ArrayBuffer。

It’s nice to see arrays have been improved in JavaScript. Now they are fast, efficient, robust, and smart enough while allocating memory.

很高兴看到数组在JavaScript中得到了改进。现在它们在分配内存时速度快、效率高、健壮且足够智能。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享