在遊戲引擎中的JS性能優化

優化的適用範圍

最近在作遊戲引擎性能優化,關於js執行性能有些內容拿來這裏分享。c++

首先須要明確兩點:設計模式

第一,本文討論的js性能問題,都是在大量執行的狀況下才暴露出來的。通常來講,60fps的遊戲,若是每幀須要執行2000次以上,那麼就能夠考慮本文的優化思路了。若是執行頻次沒有達到以上量級,性能並不會有明顯提高。數組

第二,獲得的這些性能紅利在某些狀況下須要犧牲代碼結構與可讀性,考慮在實際項目中是否值得。不少時候,須要犧牲一點點性能來使你的代碼更容易維護。切忌對項目過分優化。緩存

哪些點能夠優化

如下幾點,已經證實在大量執行的狀況下,會對項目性能產生影響。性能優化

函數調用

設計模式和重構原則常常告訴咱們:bash

一個函數只作一件事,若是幾個函數都在作一件事,那麼抽象出一個對象架構

是的,這些原則在業務邏輯開發中頗有用,讓代碼更加容易維護,同時對性能幾乎沒有什麼影響。可是,某些狀況下它並非毫無性能開銷的。當執行太多的函數調用後,你會發現性能變得很慢。每當我減小一些函數調用(把函數中的代碼直接拷貝到當前調用位置),性能都會有提高。app

例如,在渲染引擎中常常會有矩陣計算,咱們通常會抽象出一個matrix類:ide

matrix1.append(matrix2)
複製代碼

來實現矩陣相乘,但若是這段代碼是在主渲染循環中,你可能就要考慮展開成以下的樣子:函數

matrix1.a = matrix2.a * matrix1.a + matrix2.b * matrix1.c;
// ...
複製代碼

這樣性能真的會有一些提高(大量執行的狀況下),若是你的函數調用路徑過長,建議進行一些流程簡化。

除非極特殊的狀況,要從架構上減小函數調用,不要粗暴地拆散函數

做用域鏈

js在查找變量的時候,會從當前做用域開始依次向上層查找。所以能夠推斷出,局部變量的訪問永遠是最快的,全局變量的訪問永遠是最慢的。

以下例:

var array = [];
function getName() {
    array[0] = 0;
    array[1] = 0;
    array[2] = 0;
    array[3] = 0;
    // ...
}
複製代碼

性能會低於:

var array = [];
function getName() {
    var array = array;
    array[0] = 0;
    array[1] = 0;
    array[2] = 0;
    array[3] = 0;
    // ...
}
複製代碼

若是在一個函數中屢次調用全局變量或外層變量,記得先把它保存到局部變量。

this關鍵字

屢次調用this關鍵字會讓你的js執行很慢。由於當你訪問一個對象屬性,js執行時就要去查找原型鏈,直到查找到該屬性爲止。這個開銷是很大的。

下面的例子:

for(var i = 0; i < 100; i++) {
    this.array[i] = i;
}
複製代碼

性能低於:

var array = this.array;
for(var i = 0; i < 100; i++) {
    array[i] = i;
}
複製代碼

若是屢次訪問對象屬性(甚至循環調用),建議先把該屬性保存到一個局部變量中,再使用。

數學運算

js的四則運算中,除法是最慢的,乘法其次。Math封裝的數學函數中,sin與cos函數執行是最慢的。

下面的例子:

// a在大部分狀況下爲0
c = a * b;
f = a * e;
複製代碼

性能低於:

// a在大部分狀況下爲0
if(a == 0) {
    c = 0;
    f = 0;
} else {
    c = a * b;
    f = a * e;
}
複製代碼

儘可能避免沒必要要的乘除運算,可能的狀況下,緩存sin和cos運算結果。pixi.js中,顯示對象的旋轉要用到三角函數計算,引擎內部進行了標髒處理。egret中,對全局的三角函數計算方法進行了查表優化

在主循環方法中仔細查找,項目中可能存在不少相似的可優化點。

數組push與pop操做

大量調用數組push與pop方法,若是這些調用出如今循環中,那麼很不幸,它會形成性能的降低。

若是在你的渲染循環中有這樣的結構:

var matrix = Matrix.create();
// do something
Matrix.release(matrix);
複製代碼

而且對象池是這樣實現的:

// 建立matrix對象
Matrix.create = function() {
    return Matrix.pool.pop() || new Matrix();
}
// 釋放matrix對象
Matrix.release = function(matrix) {
    Matrix.pool.push(matrix);
}
複製代碼

那麼你的主循環會被push與pop拖慢速度。

通常來講,引擎中會大量使用的臨時對象。特定狀況下,對於臨時對象,除了使用對象池,還能夠用一個全局的temp對象替代

例如,上例中,咱們能夠定義一個用於引擎內部臨時使用的matrix對象 $tempMatrix:

Matrix.$tempMatrix = new Matrix();
複製代碼

使用的時候只須要:

var matrix = Matrix.$tempMatrix;
// do something
matrix.identify();
複製代碼

細心的同窗可能會說,這樣重複使用一個對象,若是代碼邏輯上須要多個temp對象,上面的實現就不能解決需求了。的確,那咱們就須要用其它的方式來解決。不過對於特定狀況,上面這種優化是簡單有效的。

總結

總結來看,這些所謂的性能優化點,大部分都是js語言在運行過程當中的弱點,在其它語言中未必會重現。例如,上文提到的函數調用,在c++等語言中並不會對性能形成明顯影響。

另外,若是你們作的不是相似於引擎這樣的底層產品的話,這些東西瞭解一下也就得了。

再次強調,永遠不要過分優化!!!

相關文章
相關標籤/搜索