[轉(zhuǎn)載]ArrayBuffer與前端內(nèi)存優(yōu)化

引言

標(biāo)注是地圖最基本的元素之一遥缕,標(biāo)明了地圖每個(gè)位置或線路的名稱侥祭。在地圖 JSAPI 中,標(biāo)注的展示效果及性能也是需要重點(diǎn)解決的問(wèn)題齐婴。

新版地圖標(biāo)注的設(shè)計(jì)中舶掖,引入了 SDF ( signed distance field)重構(gòu)了整個(gè)標(biāo)注部分的代碼。新的方式需要把標(biāo)注的位置偏移尔店,避讓,三角拆分等全部由前端進(jìn)行計(jì)算主慰,不僅計(jì)算量激增嚣州,內(nèi)存的消耗也成了重點(diǎn)關(guān)注的問(wèn)題之一。

image

例如共螺,3D 場(chǎng)景下需要構(gòu)建大量的頂點(diǎn)坐標(biāo)该肴,一萬(wàn)左右的帶文字的標(biāo)注,數(shù)據(jù)量大約會(huì)達(dá)到 8 (attributes)* 5 (1個(gè)圖標(biāo) + 4個(gè)字)* 6(個(gè)頂點(diǎn)) *1E4 藐不,約為 250w 個(gè)頂點(diǎn)匀哄,使用 Float32Array 存儲(chǔ),需要的空間約為 2.5E6 *4(byte)空間(海量地圖標(biāo)注 DEMO)雏蛮。前端這樣大量的存儲(chǔ)消耗涎嚼,需要對(duì)內(nèi)存的使用十分小心謹(jǐn)慎。于是借此機(jī)會(huì)研究了一下前端內(nèi)存相關(guān)的問(wèn)題挑秉,以便在開發(fā)過(guò)程中做出更優(yōu)的選擇法梯,減少內(nèi)存消耗,提高程序性能。

01 前端內(nèi)存使用概述

首先我們來(lái)了解一下內(nèi)存的結(jié)構(gòu)立哑。

內(nèi)存結(jié)構(gòu)

內(nèi)存分為堆(heap)和棧(stack)夜惭,堆內(nèi)存存儲(chǔ)復(fù)雜的數(shù)據(jù)類型,棧內(nèi)存則存儲(chǔ)簡(jiǎn)單數(shù)據(jù)類型铛绰,方便快速寫入和讀取數(shù)據(jù)诈茧。在訪問(wèn)數(shù)據(jù)時(shí),先從棧內(nèi)尋找相應(yīng)數(shù)據(jù)的存儲(chǔ)地址捂掰,再根據(jù)獲得的地址敢会,找到堆內(nèi)該變量真正存儲(chǔ)的內(nèi)容讀取出來(lái)。

在前端中尘颓,被存儲(chǔ)在棧內(nèi)的數(shù)據(jù)包括小數(shù)值型走触,string ,boolean 和復(fù)雜類型的地址索引疤苹。

所謂小數(shù)值數(shù)據(jù)(small number), 即長(zhǎng)度短于 32 位存儲(chǔ)空間的 number 型數(shù)據(jù)互广。

一些復(fù)雜的數(shù)據(jù)類型,諸如 Array卧土,Object 等惫皱,是被存在堆中的。如果我們要獲取一個(gè)已存儲(chǔ)的對(duì)象 A尤莺,會(huì)先從棧中找到這個(gè)變量存儲(chǔ)的地址旅敷,再根據(jù)該地址找到堆中相應(yīng)的數(shù)據(jù)。如圖:

image

簡(jiǎn)單的數(shù)據(jù)類型由于存儲(chǔ)在棧中颤霎,讀取寫入速度相對(duì)復(fù)雜類型(存在堆中)會(huì)更快些媳谁。下面的 Demo 對(duì)比了存在堆中和棧中的寫入性能:

function inStack(){
    let number = 1E5;
    var a;

    while(number--){
        a = 1;
    }
}

var obj = {};
function inHeap(){
    let number = 1E5;

    while(number--){
        obj.key = 1;
    }
}

實(shí)驗(yàn)環(huán)境1:

mac OS/firefox v66.0.2
對(duì)比結(jié)果:

image

實(shí)驗(yàn)環(huán)境2:

mac OS/safari v11.1(13605.1.33.1.2)

對(duì)比結(jié)果:

image

在每個(gè)函數(shù)運(yùn)行 10w 次的數(shù)據(jù)量下,可以看出在棧中的寫入操作是快于堆的友酱。

對(duì)象及數(shù)組的存儲(chǔ)

在JS中晴音,一個(gè)對(duì)象可以任意添加和移除屬性,似乎沒(méi)有限制(實(shí)際上需要不能大于 2^32 個(gè)屬性)缔杉。而JS中的數(shù)組锤躁,不僅是變長(zhǎng)的,可以隨意添加刪除數(shù)組元素或详,每個(gè)元素的數(shù)據(jù)類型也可以完全不一樣系羞,更不一般的是,這個(gè)數(shù)組還可以像普通的對(duì)象一樣霸琴,在上面掛載任意屬性椒振,這都是為什么呢?

Object 存儲(chǔ)

首先了解一下沈贝,JS是如何存儲(chǔ)一個(gè)對(duì)象的杠人。

JS在設(shè)計(jì)復(fù)雜類型存儲(chǔ)的時(shí)候面臨的最直觀的問(wèn)題就是,選擇一種數(shù)據(jù)結(jié)構(gòu),需要在讀取嗡善,插入和刪除三個(gè)方面都有較高的性能辑莫。

數(shù)組形式的結(jié)構(gòu),讀取和順序?qū)懭氲乃俣茸羁煺忠迦牒蛣h除的效率都非常低下各吨;

鏈表結(jié)構(gòu),移除和插入的效率非常高袁铐,但是讀取效率過(guò)低揭蜒,也不可取剔桨;

復(fù)雜一些的樹結(jié)構(gòu)等等屉更,雖然不同的樹結(jié)構(gòu)有不同的優(yōu)點(diǎn),但都繞不過(guò)建樹時(shí)較復(fù)雜洒缀,導(dǎo)致初始化效率低下瑰谜;

綜上所屬,JS 選擇了一個(gè)初始化树绩,查詢和插入刪除都能有較好萨脑,但不是最好的性能的數(shù)據(jù)結(jié)構(gòu) -- 哈希表。

哈希表

哈希表存儲(chǔ)是一種常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)饺饭。所謂哈希映射渤早,是把任意長(zhǎng)度的輸入通過(guò)散列算法變換成固定長(zhǎng)度的輸出。

對(duì)于一個(gè) JS 對(duì)象瘫俊,每一個(gè)屬性鹊杖,都按照一定的哈希映射規(guī)則,映射到不同的存儲(chǔ)地址上扛芽。在我們尋找該屬性時(shí)仅淑,也是通過(guò)這個(gè)映射方式,找到存儲(chǔ)位置胸哥。當(dāng)然,這個(gè)映射算法一定不能過(guò)于復(fù)雜赡鲜,這會(huì)使映射效率低下空厌;但也不能太簡(jiǎn)單,過(guò)于簡(jiǎn)單的映射方式银酬,會(huì)導(dǎo)致無(wú)法將變量均勻的映射到一片連續(xù)的存儲(chǔ)空間內(nèi)嘲更,而造成頻繁的哈希碰撞。

關(guān)于哈希的映射算法有很多著名的解決方案揩瞪,此處不再展開赋朦。

哈希碰撞

所謂哈希碰撞,指的是在經(jīng)過(guò)哈希映射計(jì)算后,被映射到了相同的地址宠哄,這樣就形成了哈希碰撞壹将。想要解決哈希碰撞,則需要對(duì)同樣被映射過(guò)來(lái)的新變量進(jìn)行處理毛嫉。

眾所周知诽俯,JS 的對(duì)象是可變的,屬性可在任意時(shí)候(大部分情況下)添加和刪除承粤。在最開始給一個(gè)對(duì)象分配內(nèi)存時(shí)暴区,如果不想出現(xiàn)哈希碰撞問(wèn)題,則需要分配巨大的連續(xù)存儲(chǔ)空間辛臊。但大部分的對(duì)象所包含的屬性一般都不會(huì)很長(zhǎng)仙粱,這就導(dǎo)致了極大的空間浪費(fèi)。

但是如果一開始分配的內(nèi)存較少彻舰,隨著屬性數(shù)量的增加伐割,必定會(huì)出現(xiàn)哈希碰撞,那如何解決哈希碰撞問(wèn)題呢淹遵?

對(duì)于哈希碰撞問(wèn)題口猜,比較經(jīng)典的解決方法有如下幾種:

  • 開放尋址法
  • 再哈希法
  • 拉鏈法

這幾種方式均各有優(yōu)略,由于本文不是重點(diǎn)講述哈希碰撞便不再綴余透揣。

在 JS 中济炎,選擇的是拉鏈法解決哈希碰撞。所謂拉鏈法辐真,是將通過(guò)一定算法得到的相同映射地址的值须尚,用鏈表的形式存儲(chǔ)起來(lái)。如圖所示(以傾斜的箭頭表明鏈表動(dòng)態(tài)分配侍咱,并非連續(xù)的內(nèi)存空間):

image

映射后的地址空間存儲(chǔ)的是一個(gè)鏈表的指針耐床,一個(gè)鏈表的每個(gè)單元,存儲(chǔ)著該屬性的 key, value 和下一個(gè)元素的指針楔脯;

這種存儲(chǔ)的方式的好處是撩轰,最開始不需要分配較大的存儲(chǔ)空間,新添加的屬性只要?jiǎng)討B(tài)分配內(nèi)存即可昧廷;

對(duì)于索引堪嫂,添加和移除都有相對(duì)較好的性能;

通過(guò)上述介紹木柬,也就解釋了這個(gè)小節(jié)最開始提出的為何JS 的對(duì)象如此靈活的疑問(wèn)皆串。

Array 存儲(chǔ)

JS 的數(shù)組為何也比其他語(yǔ)言的數(shù)組更加靈活呢?因?yàn)?JS 的 Array 的對(duì)象眉枕,就是一種特殊類型的數(shù)組恶复!

所謂特殊類型怜森,就是指在 Array 中,每一個(gè)屬性的 key 就是這個(gè)屬性的 index谤牡;而這個(gè)對(duì)象還有 .length 屬性副硅;還有 concat, slice, push, pop 等方法;

于是這就解釋了:

  1. 為何 JS 的數(shù)組每個(gè)數(shù)據(jù)類型都可以不一樣拓哟?
    因?yàn)樗褪莻€(gè)對(duì)象想许,每條數(shù)據(jù)都是一個(gè)新分配的類型連入鏈表中;
  2. 為何 JS 的數(shù)組無(wú)需提前設(shè)置長(zhǎng)度断序,是可變數(shù)組流纹?
    答案同上;
  3. 為何數(shù)組可以像 Object 一樣掛載任意屬性违诗?
    因?yàn)樗褪莻€(gè)對(duì)象漱凝;

等等一系列的問(wèn)題。

內(nèi)存攻擊

當(dāng)然诸迟,選擇任何一種數(shù)據(jù)存儲(chǔ)方式茸炒,都會(huì)有其不利的一面。這種哈希的拉鏈算法在極端情況下也會(huì)造成嚴(yán)重的內(nèi)存消耗阵苇。

我們知道壁公,良好的散列映射算法,可以講數(shù)據(jù)均勻的映射到不同的地址绅项。但如果我們掌握了這種映射規(guī)律而將不同的數(shù)據(jù)都映射到相同的地址所對(duì)應(yīng)的鏈表中去紊册,并且數(shù)據(jù)量足夠大,將造成內(nèi)存的嚴(yán)重?fù)p耗快耿。讀取和插入一條數(shù)據(jù)會(huì)中了鏈表的缺陷囊陡,從而變得異常的慢,最終拖垮內(nèi)存掀亥。這就是我們所說(shuō)的內(nèi)存攻擊撞反。

構(gòu)造一個(gè) JSON 對(duì)象歌殃,使該對(duì)象的 key 大量命中同一個(gè)地址指向的列表抹竹,附件為 JS 代碼籽御,只包含了一個(gè)特意構(gòu)造的對(duì)象(引用出處)拟糕,圖二為利用 Performance 查看的性能截圖:

image

相同 size 對(duì)象的 Performance 對(duì)比圖:

image

根據(jù) Performance 的截圖來(lái)看,僅僅是 load 一個(gè) size 為 65535 的對(duì)象俺附,竟然足足花費(fèi)了 40 s辆脸!而相同大小的非共計(jì)數(shù)據(jù)的運(yùn)行時(shí)間可忽略不計(jì)话原。

如果被用戶利用了這個(gè)漏洞倚聚,構(gòu)建更長(zhǎng)的 JSON 數(shù)據(jù),可以直接把服務(wù)端的內(nèi)存打滿凿可,導(dǎo)致服務(wù)不可用惑折。這些地方都需要開發(fā)者有意識(shí)的避免授账。

但從本文的來(lái)看,這個(gè)示例也很好的驗(yàn)證了我們上面所說(shuō)的對(duì)象的存儲(chǔ)形式惨驶。

02 視圖類型(連續(xù)內(nèi)存)

通過(guò)上面的介紹與實(shí)驗(yàn)可以知道白热,我們使用的數(shù)組實(shí)際上是偽數(shù)組。這種偽數(shù)組給我們的操作帶來(lái)了極大的方便性粗卜,但這種實(shí)現(xiàn)方式也帶來(lái)了另一個(gè)問(wèn)題屋确,及無(wú)法達(dá)到數(shù)組快速索引的極致,像文章開頭時(shí)所說(shuō)的上百萬(wàn)的數(shù)據(jù)量的情況下续扔,每次新添加一條數(shù)據(jù)都需要?jiǎng)討B(tài)分配內(nèi)存空間攻臀,數(shù)據(jù)索引時(shí)都要遍歷鏈表索引造成的性能浪費(fèi)會(huì)變得異常的明顯。

好在 ES6 中纱昧,JS 新提供了一種獲得真正數(shù)組的方式:ArrayBuffer刨啸,TypedArray 和 DataView

ArrayBuffer

ArrayBuffer 代表分配的一段定長(zhǎng)的連續(xù)內(nèi)存塊。但是我們無(wú)法直接對(duì)該內(nèi)存塊進(jìn)行操作识脆,只能通過(guò) TypedArray 和 DataView 來(lái)對(duì)其操作设联。

TypedArray

TypeArray 是一個(gè)統(tǒng)稱,他包含 Int8Array / Int16Array / Int32Array / Float32Array等等灼捂。詳細(xì)請(qǐng)見(jiàn):

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray

拿 Int8Array 來(lái)舉例离例,這個(gè)對(duì)象可拆分為三個(gè)部分:Int、8悉稠、Array

首先這是一個(gè)數(shù)組宫蛆,這個(gè)數(shù)據(jù)里存儲(chǔ)的是有符號(hào)的整形數(shù)據(jù),每條數(shù)據(jù)占 8 個(gè)比特位偎球,及該數(shù)據(jù)里的每個(gè)元素可表示的最大數(shù)值是 2^7 = 128 , 最高位為符號(hào)位洒扎。

// TypedArray
var typedArray = new Int8Array(10);

typedArray[0] = 8;
typedArray[1] = 127;
typedArray[2] = 128;
typedArray[3] = 256;

console.log("typedArray","   -- ", typedArray );
//Int8Array(10) [8, 127, -128, 0, 0, 0, 0, 0, 0, 0]

其他類型也都以此類推,可以存儲(chǔ)的數(shù)據(jù)越長(zhǎng)衰絮,所占的內(nèi)存空間也就越大袍冷。這也要求在使用 TypedArray 時(shí),對(duì)你的數(shù)據(jù)非常了解猫牡,在滿足條件的情況下盡量使用占較少內(nèi)存的類型胡诗。

DataView

DataView 相對(duì) TypedArray 來(lái)說(shuō)更加的靈活。每一個(gè) TypedArray 數(shù)組的元素都是定長(zhǎng)的數(shù)據(jù)類型淌友,如 Int8Array 只能存儲(chǔ) Int8 類型煌恢;但是 DataView 卻可以在傳遞一個(gè) ArrayBuffer 后,動(dòng)態(tài)分配每一個(gè)元素的長(zhǎng)度震庭,即存不同長(zhǎng)度及類型的數(shù)據(jù)瑰抵。

// DataView
var arrayBuffer = new ArrayBuffer(8 * 10);

var dataView = new DataView(arrayBuffer);

dataView.setInt8(0, 2);
dataView.setFloat32(8, 65535);

// 從偏移位置開始獲取不同數(shù)據(jù)
dataView.getInt8(0);
// 2
dataView.getFloat32(8);
// 65535

TypedArray 與 DataView 性能對(duì)比

DataView 在提供了更加靈活的數(shù)據(jù)存儲(chǔ)的同時(shí),最大限度的節(jié)省了內(nèi)存器联,但也犧牲了一部分性能二汛,同樣的 DataView 和 TypedArray 性能對(duì)比如下:

// 普通數(shù)組
function arrayFunc(){

var length = 2E6;
var array = [];
var index = 0;

while(length--){
    array[index] = 10;
    index ++;
}
}
// dataView
function dataViewFunc(){

var length = 2E6;
var arrayBuffer = new ArrayBuffer(length);
var dataView = new DataView(arrayBuffer);
var index = 0;
while(length--){
    dataView.setInt8(index, 10);
    index ++;
}
}
// typedArray
function typedArrayFunc(){

var length = 2E6;
var typedArray = new Int8Array(length);
var index = 0;

while(length--){
    typedArray[index++] = 10;
}
}

實(shí)驗(yàn)環(huán)境1:
mac OS/safari v11.1(13605.1.33.1.2)
對(duì)比結(jié)果:

image

實(shí)驗(yàn)環(huán)境2:

mac OS/firefox v66.0.2

對(duì)比結(jié)果:

image

在 Safari 和 firefox 下婿崭,DataView 的性能還不如普通數(shù)組快。所以在條件允許的情況下肴颊,開發(fā)者還是盡量使用 TypedArray 來(lái)達(dá)到更好的性能效果氓栈。

當(dāng)然,這種對(duì)比并不是一成不變的婿着。比如授瘦,谷歌的 V8 引擎已經(jīng)在最近的升級(jí)版本中,解決了 DataView 在操作時(shí)的性能問(wèn)題竟宋。

DataView 最大的性能問(wèn)題在于將 JS 轉(zhuǎn)成 C++ 過(guò)程的性能浪費(fèi)提完。而谷歌將該部分使用 CSA( CodeStubAssembler)語(yǔ)言重寫后,可以直接操作 TurboFan(V8 引擎)來(lái)避免轉(zhuǎn)換時(shí)帶來(lái)的性能損耗袜硫。

實(shí)驗(yàn)環(huán)境3:
mac OS / chrome v73.0.3683.86
對(duì)比結(jié)果:

image

可見(jiàn)在 chrome 的優(yōu)化下氯葬,DataView 與 TypedArray 性能差距已經(jīng)不大了,在需求需要變長(zhǎng)數(shù)據(jù)保存的情況下婉陷,DataView 會(huì)比 TypedArray 節(jié)省更多內(nèi)存帚称。

具體性能對(duì)比:
https://v8.dev/blog/dataview

03 共享內(nèi)存(多線程通訊)

共享內(nèi)存介紹

說(shuō)到內(nèi)存還不得不提的一部分內(nèi)容則是共享內(nèi)存機(jī)制。

JS 的所有任務(wù)都是運(yùn)行在主線程內(nèi)的秽澳,通過(guò)上面的視圖闯睹,我們可以獲得一定性能上的提升。但是當(dāng)程序變得過(guò)于復(fù)雜時(shí)担神,我們希望通過(guò) webworker 來(lái)開啟新的獨(dú)立線程楼吃,完成獨(dú)立計(jì)算。

開啟新的線程伴隨而來(lái)的問(wèn)題就是通訊問(wèn)題妄讯。webworker 的 postMessage 可以幫助我們完成通信孩锡,但是這種通信機(jī)制是將數(shù)據(jù)從一部分內(nèi)存空間復(fù)制到主線程的內(nèi)存下。這個(gè)賦值過(guò)程就會(huì)造成性能的消耗亥贸。

而共享內(nèi)存躬窜,顧名思義,可以讓我們?cè)诓煌木€程間炕置,共享一塊內(nèi)存荣挨,這些現(xiàn)成都可以對(duì)內(nèi)存進(jìn)行操作,也可以讀取這塊內(nèi)存朴摊。省去了賦值數(shù)據(jù)的過(guò)程默垄,不言而喻,整個(gè)性能會(huì)有較大幅度的提升甚纲。

使用原始的 postMessage 方法進(jìn)行數(shù)據(jù)傳輸

main.js

// main
var worker = new Worker('./worker.js');

worker.onmessage = function getMessageFromWorker(e){

// 被改造后的數(shù)據(jù)口锭,與原數(shù)據(jù)對(duì)比,表明數(shù)據(jù)是被克隆了一份
console.log("e.data","   -- ", e.data );
// [2, 3, 4]

// msg 依舊是原本的 msg介杆,沒(méi)有任何改變
console.log("msg","   -- ", msg );
// [1, 2, 3]

};

var msg = [1, 2, 3];

worker.postMessage(msg);

worker.js 

// worker
onmessage = function(e){

var newData = increaseData(e.data);
postMessage(newData);

};

function increaseData(data){

for(let i = 0; i < data.length; i++){
    data[i] += 1;
}

return data;

}

由上述代碼可知鹃操,每一個(gè)消息內(nèi)的數(shù)據(jù)在不同的線程中况既,都是被克隆一份以后再傳輸?shù)摹?shù)據(jù)量越大组民,數(shù)據(jù)傳輸速度越慢。

使用 sharedBufferArray 的消息傳遞

main.js

var worker = new Worker('./sharedArrayBufferWorker.js');

worker.onmessage = function(e){
// 傳回到主線程已經(jīng)被計(jì)算過(guò)的數(shù)據(jù)

console.log("e.data","   -- ", e.data );
// SharedArrayBuffer(3) {}

// 和傳統(tǒng)的 postMessage 方式對(duì)比悲靴,發(fā)現(xiàn)主線程的原始數(shù)據(jù)發(fā)生了改變

console.log("int8Array-outer","   -- ", int8Array );
// Int8Array(3) [2, 3, 4]

};

var sharedArrayBuffer = new SharedArrayBuffer(3);
var int8Array = new Int8Array(sharedArrayBuffer);

int8Array[0] = 1;
int8Array[1] = 2;
int8Array[2] = 3;

worker.postMessage(sharedArrayBuffer);

worker.js

onmessage = function(e){

var arrayData = increaseData(e.data);
postMessage(arrayData);

};

function increaseData(arrayData){

var int8Array = new Int8Array(arrayData);
for(let i = 0; i < int8Array.length; i++){
    int8Array[i] += 1;
}

return arrayData;

}

通過(guò)共享內(nèi)存?zhèn)鬟f的數(shù)據(jù)臭胜,在 worker 中改變了數(shù)據(jù)以后,主線程的原始數(shù)據(jù)也被改變了癞尚。

性能對(duì)比

實(shí)驗(yàn)環(huán)境1:

mac OS/chrome v73.0.3683.86,

10w 條數(shù)據(jù)

對(duì)比結(jié)果:

image

實(shí)驗(yàn)環(huán)境2:

mac OS/chrome v73.0.3683.86,

100w 條數(shù)據(jù)

對(duì)比結(jié)果:

image

從對(duì)比圖中來(lái)看耸三,10w 數(shù)量級(jí)的數(shù)據(jù)量,sharedArrayBuffer 并沒(méi)有太明顯的優(yōu)勢(shì)浇揩,但在百萬(wàn)數(shù)據(jù)量時(shí)仪壮,差異變得異常的明顯了。

SharedArrayBuffer 不僅可以在 webworker 中使用胳徽,在 wasm 中积锅,也能使用共享內(nèi)存進(jìn)行通信。在這項(xiàng)技術(shù)使我們的性能得到大幅度的提升時(shí)养盗,也沒(méi)有讓數(shù)據(jù)傳輸成為性能瓶頸缚陷。

但比較可惜的一點(diǎn)是,SharedArrayBuffer 的兼容性比較差往核,只有 chrome 68 以上支持箫爷,firefox 在最新版本中雖然支持,但需要用戶主動(dòng)開啟聂儒;在 safari 中甚至還不支持該對(duì)象虎锚。

04 內(nèi)存檢測(cè)及垃圾回收機(jī)制

為了保證內(nèi)存相關(guān)問(wèn)題的完整性,不能拉下內(nèi)存檢測(cè)及垃圾回收機(jī)制衩婚。不過(guò)這兩個(gè)內(nèi)容都有非常多介紹的文章窜护,這里不再詳細(xì)介紹。

內(nèi)存檢測(cè)

介紹了前端內(nèi)存及相關(guān)性能及使用優(yōu)化后谅猾。最重要的一個(gè)環(huán)節(jié)就是如何檢測(cè)我們的內(nèi)存占用了柄慰。chrome 中通常都是使用控制臺(tái)的 Memory 來(lái)進(jìn)行內(nèi)存檢測(cè)及分析。

使用內(nèi)存檢測(cè)的方式參見(jiàn):

https://developers.google.com/web/tools/chrome-devtools/memory-problems/heap-snapshots?hl=zh-cn

垃圾回收機(jī)制

JS 語(yǔ)言并不像諸如 C++ 一樣需要手動(dòng)分配內(nèi)存和釋放內(nèi)存税娜,而是有自己一套動(dòng)態(tài) GC 策略的坐搔。通常的垃圾回收機(jī)制有很多種。

前端用到的方式為標(biāo)記清除法敬矩,可以解決循環(huán)引用的問(wèn)題:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management#垃圾回收

05 結(jié)束語(yǔ)

在了解了前端內(nèi)存相關(guān)機(jī)制后概行,創(chuàng)建任意數(shù)據(jù)類型時(shí),我們可以在貼近場(chǎng)景的情況下去選擇更合適的方式保有數(shù)據(jù)弧岳。例如:

在數(shù)據(jù)量不是很大的情況下凳忙,選擇操作更加靈活的普通數(shù)組业踏;

在大數(shù)據(jù)量下,選擇一次性分配連續(xù)內(nèi)存塊的類型數(shù)組或者 DataView涧卵;

不同線程間通訊勤家,數(shù)據(jù)量較大時(shí)采用 sharedBufferArray 共享數(shù)組;

使用 Memory來(lái)檢測(cè)是否存在內(nèi)存問(wèn)題柳恐,了解了垃圾回收機(jī)制伐脖,減少不必要的 GC 觸發(fā)的 CPU 消耗。

再結(jié)合我們的地圖標(biāo)注改版來(lái)說(shuō)乐设,為了節(jié)省內(nèi)存動(dòng)態(tài)分配造成的消耗讼庇,量級(jí)巨大的數(shù)據(jù)均采用的 TypedArray 來(lái)存儲(chǔ)。另外近尚,大部分的數(shù)據(jù)處理蠕啄,也都在 worker 內(nèi)進(jìn)行。為了減少 GC戈锻,將大量的循環(huán)內(nèi)變量聲明全部改成外部一次性的聲明等等歼跟,這些都對(duì)我們的性能提升有了很大的幫助。

最后舶沛,這些性能測(cè)試的最終結(jié)果并非一成不變(如上面 chrome 做的優(yōu)化)嘹承,但原理基本相同。所以如庭,如果在不同的時(shí)期和不同的平臺(tái)上想要得到相對(duì)準(zhǔn)確的性能分析叹卷,還是自己手動(dòng)寫測(cè)試用例來(lái)得靠譜。

本文作者:高德技術(shù)小哥
原文地址:https://zhuanlan.zhihu.com/p/91231433

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坪它,一起剝皮案震驚了整個(gè)濱河市骤竹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌往毡,老刑警劉巖蒙揣,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異开瞭,居然都是意外死亡懒震,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門嗤详,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)个扰,“玉大人,你說(shuō)我怎么就攤上這事葱色〉菡” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)办龄。 經(jīng)常有香客問(wèn)我烘绽,道長(zhǎng),這世上最難降的妖魔是什么俐填? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任安接,我火速辦了婚禮,結(jié)果婚禮上英融,老公的妹妹穿的比我還像新娘赫段。我一直安慰自己,他們只是感情好矢赁,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贬丛,像睡著了一般撩银。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上豺憔,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天额获,我揣著相機(jī)與錄音,去河邊找鬼恭应。 笑死抄邀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昼榛。 我是一名探鬼主播境肾,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼胆屿!你這毒婦竟也來(lái)了奥喻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤非迹,失蹤者是張志新(化名)和其女友劉穎环鲤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憎兽,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冷离,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纯命。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片西剥。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扎附,靈堂內(nèi)的尸體忽然破棺而出蔫耽,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布匙铡,位于F島的核電站图甜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鳖眼。R本人自食惡果不足惜黑毅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钦讳。 院中可真熱鬧矿瘦,春花似錦、人聲如沸愿卒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琼开。三九已至易结,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柜候,已是汗流浹背搞动。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渣刷,地道東北人鹦肿。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像辅柴,于是被迫代替她去往敵國(guó)和親箩溃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • [TOC] 前端加解密 參考文檔:SubtleCrypto:https://developer.mozilla.o...
    johnzhu12閱讀 621評(píng)論 0 0
  • 今天無(wú)意間看到這篇關(guān)于內(nèi)存優(yōu)化的文章碌嘀,我認(rèn)為寫的很不錯(cuò)碾篡,所以就想保留下來(lái)。轉(zhuǎn)載的文章出處:https://blog...
    一個(gè)冬季閱讀 245評(píng)論 2 0
  • 聲明:本文內(nèi)容來(lái)自《Redis開發(fā)與運(yùn)維》一書第八章筏餐,如轉(zhuǎn)載請(qǐng)聲明开泽。Redis所有的數(shù)據(jù)都在內(nèi)存中,而內(nèi)存又是非常...
    yoqu閱讀 1,502評(píng)論 0 2
  • 內(nèi)存優(yōu)化 Redis所有的數(shù)據(jù)都在內(nèi)存中,而內(nèi)存又是非常寶貴的資源导俘。如何優(yōu)化內(nèi)存的使用一直是Redis用戶非常關(guān)注...
    linuxzw閱讀 423評(píng)論 0 2
  • 參考來(lái)源 Redis的內(nèi)存優(yōu)化 Redis所有的數(shù)據(jù)都在內(nèi)存中峦耘,而內(nèi)存又是非常寶貴的資源。對(duì)于如何優(yōu)化內(nèi)存使用一直...
    秦漢郵俠閱讀 1,293評(píng)論 0 2