《圖解 C# 教程 第 5 版》與效能優化(附 Unity 專案)
這本書仍然是入門 C# 最好的一本書。
這本書新版出來的時候我十分關注,於是英子姐送了一本給我,本文也是答應英子姐所寫的一篇文章。她一開始還問我“你現在還需要看這本入門書嗎?”,我認為是的。工作了遇到了不少問題,大都跟自己基礎不牢有關係。
這本書以圖形為載體,生動地介紹了 C# 語言本身。其中圖形對我們瞭解 C# 語法在記憶體中的本質十分有幫助,非同步、異常等章節中的處理流程圖也很清晰明瞭,這也是我看重的一點。
在記憶體中的形態
為什麼要了解 C# 在記憶體中的形態呢?
書中第四章介紹記憶體區域的棧中,有一句話說的很好:
作為程式設計師,你不需要顯式地對它做任何事情。但瞭解棧的基本功能可以更好地瞭解程式在執行時在做什麼,並能更好地瞭解 C# 文件和著作。
遊戲開發中,除了業務邏輯,我們還會更關注遊戲的效能本身。我們需要保證遊戲能流暢執行在大部分機型上,保證每一幀能流暢地播放,例如 CPU 需要處理渲染程式碼、物理模擬、動畫回撥等等,其中我們的程式碼也有可能引起效能問題。我們需要更瞭解執行程式碼的代價,例如:
- 這些程式碼產生了多少 GC
- GC 只會產生一次還是每幀都會產生
- 在極端情況下程式碼的效能如何
- 是否使用了正確的資料結構
- Unity API 或者一些庫 API 的背後到底做了什麼
- ...
這本書相對上一版多了 .Net Core, C# 7.0 語法的講解,對於我而言,重溫的是第 4、7、11、13、15、17、19 和 27 章節,這些內容是我工作中經常要接觸、著手優化的地方。書中對於非同步程式設計也介紹地很好,但對於我來說,反射、非同步程式設計、新增語法等到以後有需要再看也不遲。
指令碼的效能優化,無非是用更合適的程式碼去實現需求,不必要的記憶體都給我吐出來!(注:作者在生活中並沒有這麼吝嗇)
下面會列舉一些程式碼寫法的效能對比。
結構和類
這其實也是用棧還是用堆的考量。
垃圾回收
Unity 用的是 mono 虛擬機器,其堆的記憶體是通過垃圾回收演算法 Boehm GC 來管理的,其不分代(Non-generational)和非壓縮式(Non-compacting)的特性,導致了我們平常要注意避免載入過多的小記憶體,從而記憶體碎片化(Memory fragmentation)。
分代:大塊記憶體、小記憶體、超小記憶體是分在不同記憶體區域來進行管理。還有長久記憶體,當有一個記憶體很久沒動的時候會移到長久記憶體區域中,從而省出記憶體給更頻繁分配的記憶體。
壓縮式:當有記憶體被回收的時候,壓縮記憶體會把下圖空的地方重新排布。
記憶體碎片化:記憶體過多小記憶體,導致大記憶體不能有效地被使用。
具體可以參考 Unity 文件 Understanding the managed heap
同時也推薦高川老師的演講:淺談 Unity 記憶體管理,和我看視訊時的筆記:筆記。
用結構還是類
這裡推一篇微軟官方文件:Choosing Between Class and Struct
引用型別被分配在堆上並被垃圾回收演算法管理,值型別則分配在棧上,棧會按需 unwind 來釋放他們。因此,值型別的釋放比引用型別的釋放開銷要小。
書中 11.9 小節還提到:
對結構進行分配的開銷比建立類例項小,所以使用結構代替類有時可以提高效能,但要注意裝箱和拆箱的高昂代價。
值型別陣列的分配和釋放比引用型別陣列的分配和釋放開銷也更小。
除了最基本的修改值型別和引用型別的區別外,要注意的是傳遞引數或者返回返回值的時候,值型別都會隱性地被建立,這可能也會產生沒想到的記憶體開銷。
從 .Net 記憶體分配成本的角度來說,類的物件儲存的記憶體首先需要分配 4 個位元組作為物件頭位元組(object header word),跟著再分配 4 個位元組作為方法表指標(method table pointer),這些欄位是服務於 JIT 和 CIL 的,是隱藏的分配成本。
保留在堆中所需的記憶體還會根據作業系統位數來決定:
- 32 位系統中,堆上的物件會對齊到最近 4 位元組的倍數,因此如果一個物件只有一個 byte 成員,也需要對齊佔 4 個位元組,因此這個物件總共佔堆上 12 個位元組。
- 64 位系統中,堆上的物件會對齊到最近 8 位元組的倍數,方法表指標和物件頭位元組也會分別佔 8 位元組的記憶體。
(注:平常開發我們不需要這麼摳門,上面只是一個小知識點。)
大多時候我們都會用型別來實現設計模式、框架的設計,那什麼時候使用結構體呢?我們可以遵循微軟爸爸的建議:
- 邏輯上表示單個值
- 大小小於 16 個位元組
- 不會改變值
- 不需要經常裝箱拆箱
對於一些特定的場景下,我們也可以享受值型別陣列在記憶體中線性排布的福利,例如記憶體連續、SIMD 等。Unity 的 DOTS 技術棧就是一個很好的例子。
推薦閱讀:
- 在C#中使用Struct代替Class
- 作者用 DOTS(結構體、Job System、Burst)實現 A 星尋路實現效能飛躍:y2b 搜 “Pathfinding in Unity DOTS!”
裝箱
第 17 章介紹了轉換,其中提到了裝箱拆箱。那麼裝箱的代價有多大呢?我們可以做個測試:
public const int Iterations = 100_000;
// 其他地方初始化了 Iterations 大小的隨機陣列
private int[] numberArr = null;
protected override bool MeasureTestA()
{
// 設定大小以避免自動擴容帶來的效能消耗
Stack stack = new Stack(Iterations);
for (int i = 0; i < Iterations; i++)
{
stack.Push(numberArr[i]); // int -> object 裝箱
}
return true;
}
protected override bool MeasureTestB()
{
Stack<int> genericStack = new Stack<int>(Iterations);
for (int i = 0; i < Iterations; i++)
{
genericStack.Push(numberArr[i]);
}
return true;
}
檢視 Profiler 可以看到,呼叫十次 TestA 產生了 26.7MB GC,用了 267.22 毫秒;呼叫十次 TestB 只產生了 3.8MB GC,用了 20.92 毫秒。因此大量的裝箱拆箱會導致不必要的效能消耗,而有些消耗則是完全可以避免的。
我的 Rider 外掛 Heap Allocations Viewer 也會提示我 TestA 中存在裝箱的情況。
最後
寫到這裡,發現還很多東西沒講完就已經這麼多篇幅了...
大家對於平常程式碼的不同寫法也可以測試下效能,例如:foreach 和 for、上面的裝箱拆箱、多維陣列和一位陣列的效能區別等。
上面裝箱的截圖中的測試專案我也上傳了 Github:Latias94/UnityCsharpPerformanceTest,不用 Unity 的同學也可以參考下實際的測試程式碼:UnityCsharpPerformanceTest/Assets/Scripts,自己寫個命令列專案來跑下對比。
當然我們開發中還是要以需求的變化為主,不能過早優化從而破壞程式碼的擴充套件性。
相關文章
- Unity C# 反射效能優化UnityC#反射優化
- vuejs專案效能優化總結VueJS優化
- Vue 專案效能優化 — 實踐指南Vue優化
- 網站技術架構與效能優化,附高效能思維導圖網站架構優化
- JN專案-地圖定位優化地圖優化
- 前端效能優化 --- 圖片優化前端優化
- 資訊系統專案管理師教程(第3版)專案管理
- Android效能優化——圖片優化(二)Android優化
- 效能優化04-圖片優化優化
- CSAPP 5 - 優化程式效能APP優化
- RedisStack部署/持久化/安全/與C#專案整合Redis持久化C#
- C# 程式碼效能優化舉例C#優化
- iOS 圖形效能優化iOS優化
- vue-cli3專案搭建配置以及效能優化Vue優化
- SQL效能第1篇:關係優化SQL優化
- Nginx安全優化與效能調優Nginx優化
- Golang效能分析與優化Golang優化
- iOS開發——專案實戰總結&UITableView效能優化與卡頓問題iOSUIView優化
- Vue專案優化Vue優化
- IDEA 新建 Java 專案 (圖文講解, 良心教程)IdeaJava
- iOS效能優化 - 網路圖片載入優化iOS優化
- 效能優化:紋理檔案優化
- 談談 tp5 Laravel Lumen 和專案優化Laravel優化
- 如何讀懂火焰圖?+ 例項講解程式效能優化優化
- 效能調優-Mysql索引資料結構詳解與索引優化MySql索引資料結構優化
- [盤點] 專案中可以怎麼優化圖片優化
- MongoDB aggregate效能優化與排序MongoDB優化排序
- 最新《web前端開發效能優化教程》Web前端優化
- 前端效能優化之路——圖片篇。前端優化
- 在react專案中使用shouldComponentUpdate方法進行元件效能優化React元件優化
- 效能優化 (五) 長圖優化,仿微博載入長圖方式優化
- Web 頁面優化專項 > Lighthouse > 效能分數優化Web優化
- 【前端效能優化】vue效能優化前端優化Vue
- 《Flask 入門教程》第 6 章:模板優化Flask優化
- java9第5篇-Collection集合類的增強與優化Java優化
- vue專案初次優化Vue優化
- 專案效能最佳化方案
- MySQL效能優化的5個維度MySql優化