偶然翻到 INSIDE、LIMBO 開發商 Playdead 在 Unite 2016 大會中發表的演講影片與簡報檔。
發現 28:34 開始的講題【Scripting Performance】非常有意思,特此分享給大家。
在這段影片中,Playdead 的工作人員用開發 Inside 時的經驗,
分享了他們 10個可以讓 C# Script 執行效率更佳的建議。
Part 2. 連結
= 那就開始囉 =====================
1、Reduce Vector operations (減少 Vector 的運算)
1 2 3 4 5 6 7 8 | void before() { var lastPos = transform.position; transform.position = lastPos + wantedVelocity * speed * speedfactor * Mathf.Sin(someOtherFactor) * drag * friction * Time.deltaTime; } |
1 2 3 4 5 6 7 8 | void after() { var lastPos = transform.position; transform.position = lastPos + wantedVelocity * (speed * speedfactor * Mathf.Sin(someOtherFactor) * drag * friction * Time.deltaTime); } |
在 after() 與 before() 最大的差異,在於他將 speed、speedfactor、someOtherFactor、drag、friction及Time.deltatime 這些 float 變數 都先計算完後再跟 lastPos及 wantedVeloctiy 這兩個Vector3 變數做運算。 這樣的目的是 「減少在程式中進行向量值的計算次數 」。
舉例來說,在 before() 中總共進行了七次的向量計算:
同樣結果,在 after() 僅進行了兩次的向量計算:
- 第一次 :wantedVelocity *= speed*speedfactor* ...Time.deltaTime
- 第二次 :wantedVelocity += lastPos
2、Use cached Transforms (製作並使用 Transforms 快取)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | private Transform _transform; void Awake() { _transform = GetComponent<Transform>(); } public void after() { var lastPos = _transform.position; //cached in “void Start()” _transform.position = lastPos + wantedVelocity * (speed * speedfactor * Mathf.Sin(someOtherFactor) * drag * friction * Time.deltaTime); } void before() { var lastPos = transform.position; transform.position = lastPos + wantedVelocity * (speed * speedfactor * Mathf.Sin(someOtherFactor) * drag * friction * Time.deltaTime); } |
在上面的程式碼中,在 Start()階段將 Transform 這個 Class 存到 _transform 之中。
之後當有需要用到 transforms 時都直接從取_transforms變數。
按照 Playdead 在影片中口述解釋,直接向Unity調用 transform 時(如下程式碼)
var lastPos = transform.position;
Unity 為了安全起見並不單純的回傳 transform資料而已,
所以為了提升效能我們可以在 start() 階段先儲存一個快取用的 transform ,
減少反覆呼叫時多餘的浪費。
P.S 這部份我原先看不懂怎麼製做快取,後來直到 Google 到 ...【這篇】
3、Use localPosition (if possible) (盡量使用 localPosition )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | private Transform _transform; void Awake() { _transform = GetComponent<Transform>(); } void after() { var lastPos = _transform.localPosition; _transform.localPosition = lastPos + wantedVelocity * (speed * speedfactor * Mathf.Sin(someOtherFactor) * drag * friction * Time.deltaTime); } void before() { var lastPos = _transform.position; //cached in “void Start()” _transform.position = lastPos + wantedVelocity * (speed * speedfactor * Mathf.Sin(someOtherFactor) * drag * friction * Time.deltaTime); } |
應該不難想像用 localPosition 會比起時用 position 來的省效能,因為 Unity 平時是儲存LocalPosition的Data,然後有需要使用世界座標時再轉換依照子母層級作轉換。
因此直接使用LocalPosition 時可以省去子母階層的相關計算。
P.S01 雖然是這麼講...但我自己的實驗沒有差多少效能耶@@
P.S02 但是在 Playdead 的簡報中,效能的提升非常顯著 (如下圖)
P.S03 也許發佈成執行檔版本才會有所差異
4、Reduce engine calls (減少向 Unity 查找資料的頻率 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public Vector3 cachedLocalPosition = Vector3.zero; void Awake() { _transform = GetComponent<Transform>(); } public void after() { cachedLocalPosition += wantedVelocity * (speed * speedfactor * Mathf.Sin(someOtherFactor) * drag * friction * Time.deltaTime); _transform.localPosition = cachedLocalPosition; } public void before() { var lastPos = _transform.localPosition; _transform.localPosition = lastPos + wantedVelocity * (speed * speedfactor * Mathf.Sin(someOtherFactor) * drag * friction * Time.deltaTime); } |
Playdead 提醒大家盡量減少向引擎查找資料。
例如在上面的程式碼中將 before() 中的
var lastPos = _transform.localPosition;
在 after() 中的改為額外透過下面這個全域變數作儲存
public Vector3 cachedLocalPosition = Vector3.zero;
這樣的好處是當我們要索引物件的座標時,
不用多一層透過 Transform 這個 Class 來索取座標。(ㄜ 這樣講應該沒問題吧?)
5、Hmmm… Are getters and setters slow? ( 使用 get、set 會有額外耗能)
1 2 3 4 5 6 7 8 9 | public class ScriptPerformanceTest : MonoBehaviour { public float speed { get; set; } public float speedfactor { get; set; } public float someOtherFactor { get; set; } public float drag { get; set; } public float friction { get; set; } } |
1 2 3 4 5 6 7 8 9 | public class ScriptPerformanceTest : MonoBehaviour { public float speed; public float speedfactor; public float someOtherFactor; public float drag; public float friction; } |
C# 中提供了非常實用的 get、set ,讓我們可以更自由的去設定變數的可存取範圍。
但是如果非必要的情況下可以減少這工具的使用,可以再省下些效能唷!
==============================
作者已經移除這則留言。
回覆刪除