首頁  >  文章  >  web前端  >  JavaScript數組進化與效能分析

JavaScript數組進化與效能分析

小云云
小云云原創
2018-02-09 13:48:111157瀏覽

正式開始前需要聲明,本文並不是講解 JavaScript 陣列基礎知識,也不會涉及文法和使用案例。本文講得更多的是記憶體、最佳化、文法差異、效能、近來的演進。本文主要與大家介紹JavaScript 陣列的進化與效能分析,本文講得更多的是記憶體、優化、語法差異、效能、近來的演進。需要的朋友可以參考下,希望能幫助大家。

在使用 JavaScript 前,我對 C、C++、C# 這些已經頗為熟悉。就像許多 C/C++ 開發者一樣,JavaScript 給我的第一印象並不好。

Array 是主要原因之一。 JavaScript 陣列不是連續(contiguous)的,實作類似雜湊映射(hash-maps)或字典(dictionaries)。我覺得這有點像是 B 級語言,陣列實作根本不恰當。自那以後,JavaScript 和我對它的理解都發生了變化,很多變化。

為什麼說 JavaScript 陣列不是真正的陣列

在聊 JavaScript 之前,先講講 Array 是什麼。

陣列是一串連續的記憶體位置,用來保存某些值。注意重點,「連續」(continuous,或 contiguous),這很重要。

上圖展示了陣列在記憶體中儲存方式。這個陣列保存了 4 個元素,每個元素 4 個位元組。加起來總共佔用了 16 個位元組的記憶體區。

假設我們宣告了 tinyInt arr[4];,分配到的記憶體區的位址從 1201 開始。一旦需要讀取 arr[2],只需要透過數學計算拿到 arr[2] 的位址。計算 1201 + (2 X 4),直接從 1209 開始讀取即可。

JavaScript 中的資料是哈希映射,可以使用不同的資料結構來實現,如鍊錶。所以,如果在 JavaScript 中宣告一個陣列 var arr = new Array(4),計算機將產生類似上圖的結構。如果程式需要讀取 arr[2],則需要從 1201 開始遍歷定址。

以上急速 JavaScript 陣列與真實陣列的不同之處。顯而易見,數學計算比遍歷鍊錶快。就長數組而言,情況尤其如此。

JavaScript 陣列的進化

不知你是否記得我們對朋友入手的 256MB 記憶體的電腦羨慕得要死的日子?而今天,8GB 記憶體遍地都是。

與此類似,JavaScript 這門語言也進化了不少。從 V8、SpiderMonkey 到 TC39 和與日俱增的 Web 用戶,巨大的努力已經使 JavaScript 成為世界級必需品。一旦有了龐大的用戶基礎,效能提升自然就是硬需求。

實際上,現代 JavaScript 引擎是會為陣列分配連續記憶體的 —— 如果陣列是同質的(所有元素類型相同)。優秀的程式設計師總是會保證陣列同質,以便 JIT(即時編譯器)能夠使用 c 編譯器式的計算方法讀取元素。

不過,一旦你想要在某個同質數組中插入一個其他類型的元素,JIT 將解構整個數組,並按照舊有的方式重新創建。

因此,如果你的程式碼寫得不太糟,JavaScript Array 物件在幕後依然保持著真正的陣列形式,這對現代 JS 開發者來說極為重要。

此外,陣列跟隨 ES2015/ES6 有了更多的演進。 TC39 決定引入類型化陣列(Typed Arrays),所以我們就有了 ArrayBuffer。

ArrayBuffer 提供一塊連續記憶體供我們隨意操作。然而,直接操作記憶體還是太複雜、偏底層。於是便有了處理 ArrayBuffer 的視圖(View)。目前已有一些可用視圖,未來還會有更多加入。

var buffer = new ArrayBuffer(8);
var view  = new Int32Array(buffer);
view[0] = 100;

高效能、高效率的類型化陣列在 WebGL 之後被引入。 WebGL 工作者遇到了極大的效能問題,即如何有效地處理二進位資料。另外,你也可以使用 SharedArrayBuffer 在多個 Web Worker 流程​​之間共享數據,以提升效能。

從簡單的雜湊映射到現在的 SharedArrayBuffer,這相當棒吧?

舊式陣列 vs 類型化陣列:效能

前面已經討論了 JavaScript 陣列的演進,現在來測試現代陣列到底能為我們帶來多大收益。以下是我在 Mac 上使用 Node.js 8.4.0 進行的一些微型測試結果。

舊式陣列:插入

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");

用時:55ms

Typed Array:插入
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");

用時:52ms

擦,我看到了什麼?舊式數組和 ArrayBuffer 的性能不相上下?不不不。請記住,前面提到過,現代編譯器已經被智慧化,能夠將元素類型相同的傳統陣列在內部轉換成記憶體連續的陣列。第一個例子正是如此。儘管使用了 new Array(LIMIT),但陣列實際上仍以現代陣列形式存在。

接著修改第一例子,將陣列改成異構型(元素型別不完全一致)的,來看看是否有效能差異。

旧式数组:插入(异构)
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");

用時:1207ms

改变发生在第 3 行,添加一条语句,将数组变为异构类型。其余代码保持不变。性能差异表现出来了,慢了 22 倍。

旧式数组:读取

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");

用时:196ms

Typed Array:读取
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");

用时:27ms

结论

类型化数组的引入是 JavaScript 发展历程中的一大步。Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,这些是类型化数组视图,使用原生字节序(与本机相同)。我们还可以使用 DataView 创建自定义视图窗口。希望未来会有更多帮助我们轻松操作 ArrayBuffer 的 DataView 库。

JavaScript 数组的演进非常 nice。现在它们速度快、效率高、健壮,在内存分配时也足够智能。

相关推荐:

实例详解javascript数组去重的几种思路

JavaScript数组去重的几种方法分享

全面解析Javascript数组方法

以上是JavaScript數組進化與效能分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn