引言
標(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)題之一。
例如共螺,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ù)。如圖:
簡(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é)果:
實(shí)驗(yàn)環(huán)境2:
mac OS/safari v11.1(13605.1.33.1.2)
對(duì)比結(jié)果:
在每個(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)存空間):
映射后的地址空間存儲(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 等方法;
于是這就解釋了:
- 為何 JS 的數(shù)組每個(gè)數(shù)據(jù)類型都可以不一樣拓哟?
因?yàn)樗褪莻€(gè)對(duì)象想许,每條數(shù)據(jù)都是一個(gè)新分配的類型連入鏈表中; - 為何 JS 的數(shù)組無(wú)需提前設(shè)置長(zhǎng)度断序,是可變數(shù)組流纹?
答案同上; - 為何數(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 查看的性能截圖:
相同 size 對(duì)象的 Performance 對(duì)比圖:
根據(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é)果:
實(shí)驗(yàn)環(huán)境2:
mac OS/firefox v66.0.2
對(duì)比結(jié)果:
在 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é)果:
可見(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é)果:
實(shí)驗(yàn)環(huán)境2:
mac OS/chrome v73.0.3683.86,
100w 條數(shù)據(jù)
對(duì)比結(jié)果:
從對(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