吐槽 C#
好像很多人認為我是在說「C# 垃圾」,然後告訴我什麼不該摳內存……果然結尾加的話靈驗了……我實在不想爭論什麼 C# 好不好之類的東西,如果你持有這種觀點,我承認我是傻逼,請不要評論。
- 分配內存於無形
典型 API:Encoding.GetString/BitsConverter/Split
這類 API 的特徵就是無論如何都會在函數內部去分配這個內存,內存根本無法重用。object.ToString() 其實也有同樣的問題:強行分配出一個 string,然後再用這個臨時的 string 去和其它的拼接。我認為理想的 API 應該是 void ToString(StringBuilder)。IFormatProvider 也是一樣的問題:
public string Format(string fmt, object arg, IFormatProvider formatProvider) n
這裡的接受的 fmt,返回的 string,都是無謂的內存分配,理想的介面應該是:
public void Format(StringBuilder builder, IReadOnlySpan<char> fmt, object arg, IFormatProvider formatProvider)n
而對於 Split,我認為理想的返回值類型是 IEnumerable<IReadOnlySpan<char>>。
像 C++ 標準庫就很少有這樣的問題,因為標準庫對於動態內存分配十分謹慎。不過幸好 .NET Core 2.1 要迎來一大波基於 Span 的 API[1]。
還有更恐怖的 boxing,那才真的是分配內存於無形。典型的如 string.Format 裝箱滿天飛。
- 抽象與性能
典型:System.Linq
System.Linq 很多時候也會在背地裡發生很多內存分配。舉個例子,Enumerable.Reverse 會把整個 Source 拷貝進分配的 buffer 里,然後 reverse[2];再例如 Enumerable.Except[3],會在內部分配一個 Set<T>,將 source 的內容全部添加進去。另一方面,基本的 Select、Where 根本優化不掉,虛函數滿天飛,性能直線下降。而 C++ 的 range-v3、Rust 的 Iterator 可以做到 Zero cost Abstraction,使用這些設施和裸寫循環效果一致[4]。
另外如果你看過 JIT 後的代碼就會發現 foreach (var v in list) 根本優化不掉,會按部就班地調用 GetEnumerator、Dispose 等。(foreach Array、string 會在 Roslyn 層翻譯為 for (int i = 0; i < .Count; ++i))[5]。
而且令人震驚的是,使用 where new() 也會造成性能損失[6]!
- 歪路
C# 自從脫離了 Anders 就已經失去了控制。加一些 readonly ref conditional ref 這些不明所以的玩意[7],真的是沖著 C++++ 努力啊。最關鍵的 Source Generator[8] (最有用的 feature,沒有之一)無人問津,令人失望。
- 雜項……
.NET Core 2.0 的一些 API .NET Framework 4.7.1 沒有,印象深的是 Dictionary.TryAdd。而且 List.ForEach/Find/FindIndex 真的是不明所以……
using 比起 C++ 的 RAII 真的是小巫見大巫。
千萬別跟我說 C# 的 GC 多牛逼不在乎這些,也別跟我說不該在乎這「點」性能。
[1] Add initial Span/Buffer-based APIs across corefx · Issue #21281 · dotnet/corefx
[2] dotnet/corefx
[3] dotnet/corefx
[4] 看看所謂的 Zero cost abstraction
[5] dotnet/roslyn
[6] Dissecting the new() constraint in C#: a perfect example of a leaky abstraction
[7] dotnet/csharplang
[8] dotnet/roslyn
推薦閱讀:
※c#中為什麼async方法里必須還要有await?
※在C#的ArrayList中的對象可以是結構體嗎?如果可以,怎麼使用比較簡便?補充:結構體包含多個欄位。
※entity framework中怎麼通過lambda表達式生成sql語句的?
※如何判斷 string 是否為合法的 C# 變數名?
TAG:C# |