您好,登錄后才能下訂單哦!
內存分配對性能的影響是很大的,分配內存本身需要時間,垃圾回收器回收內存也需要時間,所以應該盡量避免在堆里分配內存。不過直到最近優化HoLa cantk時,我才深刻的體會到內存分配對性能的影響,其中有一個關于arguments的問題挺有意思,寫在這里和大家分享一下。
我要做的事情是用webgl實現canvas的2d API(這個話題本身也是挺有意思的,有空我們再討論),drawImage是一個重要的函數,游戲會頻繁的調用它,所以它的性能至關重要。drawImage的參數個數是可變的,它有三種形式:
第一個版本大概是這樣實現的:
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); } }
為了方便說明問題,我把測試程序獨立出來:
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的使用情況時,我發現垃圾回收的時間比例很大,一般在4%以上。當時并沒有懷疑到drawImage這個函數,理由很簡單:
這個函數很簡單,它只是一個簡單的分發函數,而drawImage9的實現相對來說要復雜得多。
這里看不出有動態內存分配,也沒有違背arguments的使用規則,只是使用了它的length屬性。
加trace_opt和trace_deopt參數運行時,drawImage被優化了,而且沒有被反優化出來。
Chrome的內存Profile只能看到沒有被釋放的對象,用它查看內存泄露比較容易。這里的問題并不是泄露,而是分配了然后又釋放了,V8采用的分代垃圾回收器,這種短時存在的對象是由年輕代回收器管理器負責的,而年輕代回收器使用的半空間(semi-space)算法,這種大量短時間生存的對象,很快會耗盡其中一半空間,這時回收器需要把存活的對象拷貝到另外一半空間中,這就會耗費大量時間,而垃圾回收時會暫停JS代碼執行,如果能避免動態內存分配,減少垃圾回收器的工作時間,就能提高程序的性能。
沒法在Chrome里查看動態分配內存的地方(呵呵,后面證實是我的無知),只好去硬著頭皮看V8 JS引擎的代碼,看看能不能找到頻繁分配內存的地方,后來找到了V8統計內存分配的代碼:
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已經有了內存分配的統計代碼,Chrome里應該有對應的接口啊。再去看Chrome的Profile相關界面,最后發現需要在設置里勾選Record heap allocation stack traces,然后使用Record heap allocations功能,查看結果時選擇Allocations,可以看到每個函數分配內存的次數。有時一個問題折騰你好久,解決之前百思不得其解,覺得難得不得了,而解決之后忍不住要苦笑,原來只是一層窗戶紙!
雖然還是不知道導致動態內存分配的原因(誰知道請告訴我),至少可以想法規避它:
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); } }
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對億速云的支持。如果你想了解更多相關內容請查看下面相關鏈接
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。