BlockingCollection(T) 性能
在我的公司有一段時間,我們使用了一種本地
ObjectPool<T>實現,它提供了對其內容的阻止訪問。它非常簡單: aQueue<T>, anobject鎖定,以及在AutoResetEvent添加項目時向“借用”執行緒發出信號。類的肉真的是這兩種方法:
public T Borrow() { lock (_queueLock) { if (_queue.Count > 0) return _queue.Dequeue(); } _objectAvailableEvent.WaitOne(); return Borrow(); } public void Return(T obj) { lock (_queueLock) { _queue.Enqueue(obj); } _objectAvailableEvent.Set(); }我們一直在使用這個和其他一些集合類,而不是提供的那些,
System.Collections.Concurrent因為我們使用的是 .NET 3.5,而不是 4.0。但最近我們發現,由於我們使用的是Reactive Extensions,實際上我們確實有可用的Concurrent命名空間(在 System.Threading.dll 中)。自然地,我認為既然
BlockingCollection<T>是Concurrent命名空間中的核心類之一,它可能會提供比我或我的隊友寫的任何東西更好的性能。所以我嘗試編寫一個非常簡單的新實現:
public T Borrow() { return _blockingCollection.Take(); } public void Return(T obj) { _blockingCollection.Add(obj); }令我驚訝的是,根據一些簡單的測試(從多個執行緒借/返回池幾千次),我們最初的實現在性能方面****明顯優於
BlockingCollection<T>。它們似乎都可以正常工作;只是我們最初的實現似乎要快得多。我的問題:
- 為什麼會這樣?可能是因為
BlockingCollection<T>提供了更大的靈活性(我知道它通過包裝一個 來工作IProducerConsumerCollection<T>),這必然會帶來性能成本?- 這只是對
BlockingCollection<T>課程的完全錯誤的使用嗎?- 如果這是對 的適當使用
BlockingCollection<T>,我只是沒有正確使用嗎?例如,Take/Add方法是否過於簡單化,是否有更好的方法來獲得相同的功能?除非有人對第三個問題有一些見解,否則看起來我們現在將堅持我們最初的實現。
這裡有幾個潛在的可能性。
首先,
BlockingCollection<T>在 Reactive Extensions 中有一個 backport,與 .NET 4 最終版本不完全相同。如果這個 backport 的性能與 .NET 4 RTM 不同,我不會感到驚訝(儘管我沒有專門分析這個集合)。許多 TPL 在 .NET 4 中的性能優於在 .NET 3.5 反向移植中。
BlockingCollection<T>話雖如此,如果您有一個生產者執行緒和一個消費者執行緒,我懷疑您的實現會表現出色。在一個生產者和一個消費者的情況下,你的鎖對整體性能的影響會更小,而重置事件是消費者端等待的一種非常有效的手段。但是,
BlockingCollection<T>它旨在允許許多生產者執行緒很好地“排隊”數據。這在您的實現中表現不佳,因為鎖定爭用將很快開始成為問題。話雖如此,我還想在這裡指出一個誤解:
…它可能會提供比我或我的隊友寫的任何東西更好的性能。
這通常是不正確的。框架集合類通常執行得非常好,但對於給定場景,通常不是性能最高的選項。話雖如此,它們往往表現良好,同時非常靈活且非常健壯。它們通常傾向於很好地擴展。“自製”集合類在特定場景中的性能通常優於框架集合,但在用於它們專門設計的場景之外的場景時往往會出現問題。我懷疑這是其中一種情況。