動(dòng)態(tài)內(nèi)存分配導(dǎo)致影響Javascript性能的問題
內(nèi)存分配對性能的影響是很大的,分配內(nèi)存本身需要時(shí)間,垃圾回收器回收內(nèi)存也需要時(shí)間,所以應(yīng)該盡量避免在堆里分配內(nèi)存。不過直到最近優(yōu)化HoLa cantk時(shí),我才深刻的體會(huì)到內(nèi)存分配對性能的影響,其中有一個(gè)關(guān)于arguments的問題挺有意思,寫在這里和大家分享一下。
我要做的事情是用webgl實(shí)現(xiàn)canvas的2d API(這個(gè)話題本身也是挺有意思的,有空我們再討論),drawImage是一個(gè)重要的函數(shù),游戲會(huì)頻繁的調(diào)用它,所以它的性能至關(guān)重要。drawImage的參數(shù)個(gè)數(shù)是可變的,它有三種形式:
- drawImage(image, x, y)
- drawImage(image, x, y, width, height)
- drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
第一個(gè)版本大概是這樣實(shí)現(xiàn)的:
function Context() { } Context.prototype.drawImage3 = function(image, x, y) { this.drawImage9(image, 0, 0, image.width, image.height, x, y, image.width, image.height); } Context.prototype.drawImage5 = function(image, dx, dy, dw, dh) { this.drawImage9(image, 0, 0, image.width, image.height, dx, dy, dw, dh); } Context.prototype.drawImage9 = function(image, sx, sy, sw, sh, dx, dy, dw, dh) { //DO IT } Context.prototype.drawImage = function(image, a, b, c, d, e, f, g, h) { var n = arguments.length; if(n === 3) { this.drawImage3(image, a, b); }else if(n === 5) { this.drawImage5(image, a, b, c, d); }else if(n === 9) { this.drawImage9(image, a, b, c, d, e, f, g, h); } }
為了方便說明問題,我把測試程序獨(dú)立出來:
var image = {width:100, height:200}; var ctx = new Context(); function test() { var a = Math.random() * 100; var b = Math.random() * 100; var c = Math.random() * 100; var d = Math.random() * 100; var e = Math.random() * 100; var f = Math.random() * 100; var g = Math.random() * 100; var h = Math.random() * 100; for(var i = 0; i < 1000; i++) { ctx.drawImage(image, a, b); ctx.drawImage(image, a, b, c, d); ctx.drawImage(image, a, b, c, d, e, f, g, h); } } window.onload = function() { function loop() { test(); requestAnimationFrame(loop); } requestAnimationFrame(loop); }
用chrome的Profile查看CPU的使用情況時(shí),我發(fā)現(xiàn)垃圾回收的時(shí)間比例很大,一般在4%以上。當(dāng)時(shí)并沒有懷疑到drawImage這個(gè)函數(shù),理由很簡單:
這個(gè)函數(shù)很簡單,它只是一個(gè)簡單的分發(fā)函數(shù),而drawImage9的實(shí)現(xiàn)相對來說要復(fù)雜得多。
這里看不出有動(dòng)態(tài)內(nèi)存分配,也沒有違背arguments的使用規(guī)則,只是使用了它的length屬性。
加trace_opt和trace_deopt參數(shù)運(yùn)行時(shí),drawImage被優(yōu)化了,而且沒有被反優(yōu)化出來。
Chrome的內(nèi)存Profile只能看到?jīng)]有被釋放的對象,用它查看內(nèi)存泄露比較容易。這里的問題并不是泄露,而是分配了然后又釋放了,V8采用的分代垃圾回收器,這種短時(shí)存在的對象是由年輕代回收器管理器負(fù)責(zé)的,而年輕代回收器使用的半空間(semi-space)算法,這種大量短時(shí)間生存的對象,很快會(huì)耗盡其中一半空間,這時(shí)回收器需要把存活的對象拷貝到另外一半空間中,這就會(huì)耗費(fèi)大量時(shí)間,而垃圾回收時(shí)會(huì)暫停JS代碼執(zhí)行,如果能避免動(dòng)態(tài)內(nèi)存分配,減少垃圾回收器的工作時(shí)間,就能提高程序的性能。
沒法在Chrome里查看動(dòng)態(tài)分配內(nèi)存的地方(呵呵,后面證實(shí)是我的無知),只好去硬著頭皮看V8 JS引擎的代碼,看看能不能找到頻繁分配內(nèi)存的地方,后來找到了V8統(tǒng)計(jì)內(nèi)存分配的代碼:
void Heap::OnAllocationEvent(HeapObject* object, int size_in_bytes) { HeapProfiler* profiler = isolate_->heap_profiler(); if (profiler->is_tracking_allocations()) { profiler->AllocationEvent(object->address(), size_in_bytes); } if (FLAG_verify_predictable) { ++allocations_count_; // Advance synthetic time by making a time request. MonotonicallyIncreasingTimeInMs(); UpdateAllocationsHash(object); UpdateAllocationsHash(size_in_bytes); if (allocations_count_ % FLAG_dump_allocations_digest_at_alloc == 0) { PrintAlloctionsHash(); } } if (FLAG_trace_allocation_stack_interval > 0) { if (!FLAG_verify_predictable) ++allocations_count_; if (allocations_count_ % FLAG_trace_allocation_stack_interval == 0) { isolate()->PrintStack(stdout, Isolate::kPrintStackConcise); } } }
HeapProfiler已經(jīng)有了內(nèi)存分配的統(tǒng)計(jì)代碼,Chrome里應(yīng)該有對應(yīng)的接口啊。再去看Chrome的Profile相關(guān)界面,最后發(fā)現(xiàn)需要在設(shè)置里勾選Record heap allocation stack traces,然后使用Record heap allocations功能,查看結(jié)果時(shí)選擇Allocations,可以看到每個(gè)函數(shù)分配內(nèi)存的次數(shù)。有時(shí)一個(gè)問題折騰你好久,解決之前百思不得其解,覺得難得不得了,而解決之后忍不住要苦笑,原來只是一層窗戶紙!
雖然還是不知道導(dǎo)致動(dòng)態(tài)內(nèi)存分配的原因(誰知道請告訴我),至少可以想法規(guī)避它:
Context.prototype.drawImage = function() { var n = arguments.length; if(n === 3) { this.drawImage3.apply(this, arguments); }else if(n === 5) { this.drawImage5.apply(this, arguments); }else if(n === 9) { this.drawImage9.apply(this, arguments); } }
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接
相關(guān)文章
await vs return vs return await關(guān)鍵區(qū)別
這篇文章主要為大家介紹了await vs return vs return await關(guān)鍵區(qū)別解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12web?worker在項(xiàng)目中的使用學(xué)習(xí)為項(xiàng)目增加亮點(diǎn)
這篇文章主要為大家介紹了web?worker使用學(xué)習(xí)來為你的項(xiàng)目增加亮點(diǎn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07微信小程序 action-sheet 反饋上拉菜單簡單實(shí)例
這篇文章主要介紹了微信小程序 action-sheet 反饋上拉菜單簡單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05四十九個(gè)javascript小知識(shí)實(shí)用技巧
這篇文章主要給大家分享得是四十九個(gè)javascript小知識(shí)實(shí)用技巧像下面文章圍繞JavaScript得各種技巧詳細(xì)介紹,需要的朋友可以參考一下,希望對你有所幫助2021-11-11高級前端必會(huì)的package.json字段知識(shí)詳解
這篇文章主要為大家介紹了高級前端必會(huì)的package.json字段知識(shí)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07微信小程序?qū)崿F(xiàn)多個(gè)按鈕toggle功能的實(shí)例
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)多個(gè)按鈕toggle功能的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-06-06動(dòng)態(tài)引入DynamicImport實(shí)現(xiàn)原理
這篇文章主要為大家介紹了動(dòng)態(tài)引入DynamicImport實(shí)現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01javascript數(shù)據(jù)類型之原始類型詳解
這篇文章主要為大家介紹了javascript數(shù)據(jù)類型之原始類型詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06JavaScript中document.activeELement焦點(diǎn)元素介紹
這篇文章主要給大家分享 JavaScript中document.activeELement焦點(diǎn)元素介紹,下面文章圍繞了document.activeElement屬性展開詳細(xì)內(nèi)容,需要的朋友可以參考一下,希望對大家有所幫助2021-11-11