Update 8/10 2016 : Unity 5.3.5p8 導入了 Mono Compiler 的初步更新解決了這個問題,如果你的專案可以升級到 5.3.6 或是 5.4 之後的版本的話以下描述的問題就不會再發生了。
官方公告:
https://forum.unity.com/threads/upgraded-c-compiler-on-5-3-5p8.417363/
因為最近有討論區的朋友提到 foreach 的 Garbage Collection 問題,所以想寫一篇為什麼 foreach 會有 Garbage Collection 的文章。這篇文章比較無趣一些,TD;LR 的話就是問題是 IDisposable
不是 IEnumerator<T>
。
Garbage Collection
Unity 在使用 foreach 的時候會產生 24 bytes 的 GC 這個問題已經傳很久了。可以用個簡單的小程式去測試:
|
|
隨便掛在一個 GameObject 下面的執行結果,在 Unity 4.7.0f1
現在最新的 Unity 5.3.1f1 上面的結果好像更糟了:
兇手是誰?
我一直以為是因為 System.Collections.Generic
底下所有的容器的 Enumerator
都被宣告成 struct ,然後 foreach 在操作的時候卻是對 IEnumerator<T>
操作 IEnumerator<T>.Current
跟 IEnumerator<T>.MoveNext()
造成了 boxing 。這周末心血來潮把 Unity 建置出來的 dll 放進 ILSpy 裡面看看,才發現以往的認知是錯的。
System.Collections.Generic 底下所有的容器的 Enumerator 都被宣告成 struct 的原因可以看 Eric Lippert(C# Compiler Team 的成員的解釋) :
http://stackoverflow.com/questions/3168311/why-do-bcl-collections-use-struct-enumerators-not-classes/3168435#3168435
基本上是效能考量
以下是範例程式
|
|
ILSpy 反組譯的結果
|
|
可以看到實際上 Unity 其實正確地使用 List<int>.Enumerator
來承接 list.GetEnumerator()
的回傳值。所以那個 boxing 到底在哪裡呢?
有了這條線索後,Google 了一下發現已經有人找到了真正的問題。
https://www.reddit.com/r/Unity3D/comments/34s0je/c_memory_and_performance_tips_for_unity/cqyf5yk/
要看到問題要把 ILSpy 的展示模式從 C# 換成 IL 模式。
|
|
可以看到 box 出現在 IL_004e 行 finally 區塊裡,結果是舊版的 Mono 對有實作 IDisposable
的 struct 呼叫 Dispose 的時候(using 關鍵字觸發的)用了 IDisposable
去 box ,這跟我之前以為的不一樣。
然後更冤的可以看一下 List<T>.Enumerator
的 Dispose 實作:
http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,d3661cf752ff3f44
因為 List<T>.Enumerator
是 value type ,所以根本就不需要特別處理。這個 Dispose 是空函式,整個 boxing 是 100% 的浪費。
Mono 對於這個 bug 的 issue 在這裡:
https://bugzilla.novell.com/show_bug.cgi?id=571010
可以看到 Mono 本家已經在 2010 6/1 修正了了這個問題,但是 Unity 還是沒有 merge 這個修正。考慮到 Unity 自己有 Mono 的 fork (https://github.com/Unity-Technologies/mono),很有可能 Unity 有對 Mono 做修改,改動到現在合併有困難。否則大家喊很久的 Mono 升級或是改用 Roslyn ,為什麼 Unity 一直無法從善如流。
我自己對於 foreach 的態度就是雖然效能較差還有少量 GC 問題,但是做取捨我還是會選 foreach 取其可讀性。要小心的是如果 foreach 放在其他的 loop 裡面的情況,累積起來還是有可能會造成問題。
參考資料:
C# memory and performance tips for Unity
原文:
http://www.somasim.com/blog/2015/04/csharp-memory-and-performance-tips-for-unity/
Reddit 討論:
https://www.reddit.com/r/Unity3D/comments/34s0je/c_memory_and_performance_tips_for_unity/
C# Memory Management for Unity Developers (part 1 of 3)
Why do BCL Collections use struct enumerators, not classes?
ILSpy