考慮這個例子:
List<int> GetLengths(ICollection<string> strings) =>
strings
.Select(s => s.Length)
.ToList();
如果Select()檢查輸入集合是否實作ICollection<T>并且它的長度是預先知道的,它也可能已經回傳ICollection<T>,這樣ToList()就會用容量初始化結果串列。相反,該串列將每個值一一相加,將其存盤 log(N) 次擴展。
在 LINQ 中沒有這樣做有什么原因嗎?
更新:由于我的建議有很多問題,以下是我的概念的一些理由:
- LINQ 已經回傳了許多不同的
IEnumerable<T>. IMO 添加另一個實作另一個介面的迭代器并沒有錯。 - 只讀
ICollection<T>不必在記憶體中具體化,它只需具有Count. 這是一個裸實作的示例,ICollection<T>其行為類似于,Enumerable.Repeat<T>()除了它呼叫委托來生成每個元素。它左右拋出例外,但股票ReadOnlyCollection<T>確實如此。 - 構造
List<T>(IEnumerable<T> collection)函式已經檢查是否collection也實作ICollection<T>了,以便串列可以提前分配其存盤空間。它不違反任何介面或約定。 - 從架構的角度來看,實作
IReadOnlyCollection<T>會更有意義,但不幸的是,它在 BCL 本身中經常被忽略,并且List<T>建構式不會檢查它。
uj5u.com熱心網友回復:
幾年前(2015 年)在此 GitHub 問題中討論了優化source.Select().ToList()以及 ,System.Linq 性能改進建議source.Select().ToDictionary()
對這些建議進行了審查,并決定不實施優化。這是該主題最后一篇文章的摘錄,Stephen Toub,2015 年 9 月 15 日。
通過這個執行緒以及在此期間發生的一堆 PR,我們確實同意我們不想將現有的集合介面暴露在 LINQ 之外,例如 Select 回傳的物件不應該實作
ICollection<T>,但我們沒問題并引入了額外的內部介面,以幫助通過內置運算子對某些資訊進行集中處理,例如由Selectimplements 實作的迭代器,IArrayProvider以便ToArray可以優化其實作以在完全了解其大小的情況下處理原始陣列,使用其索引器而不是經歷IEnumerable等在這一點上,考慮到在這個執行緒上呼叫的原始任務,看起來唯一剩下的具體專案將圍繞專門的方法,比如
ToDictionary對原始來源有更多的了解。我建議如果有人想要實作這樣的優化并進行詳細的測量,可以將這種帶有相關測量的優化作為單獨的 PR 提交,這樣的問題不需要跟蹤它們。因此,我將保持關閉狀態,而不是打開新問題。顯然,人們應該隨時為要討論的個別主題/實施的解決方案打開后續問題/ PR。
恕我直言,這些內部 LINQ 優化是非常可取的。
更新:顯然source.Select().ToList()和source.Select().ToArray()操作已在 .NET 演變的后期進行了優化,正如可以通過此演示通過實驗演示的那樣:
byte[] source = new byte[1_000_000];
long mem0 = GC.GetTotalAllocatedBytes(true);
List<byte> result = source.Select(n => (byte)1).ToList();
long mem1 = GC.GetTotalAllocatedBytes(true);
Console.WriteLine($"Allocated: {mem1 - mem0:#,0} bytes");
.NET 6 上的輸出,發布模式:
Allocated: 1,000,232 bytes
在線演示
因此,byte陣列被投影,然后
List<byte>一次性實作,沒有 log(N) 分配和擴展。
uj5u.com熱心網友回復:
如果
Select()檢查輸入集合是否實作ICollection<T>并且它的長度是預先知道的,它也可能已經ICollection<T>回傳
雖然可以,但這意味著Select必須立即回傳一個物化集合。
Select回傳一個迭代器,它可以通過其他 LINQ 方法(例如Where)傳遞,同時仍然只需要源的一次迭代。
在許多查詢中,在每個階段回傳一個集合可能效率較低。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/525625.html
標籤:C#林克
