Dot-Net

BlockingCollection(T) 性能

  • June 14, 2010

在我的公司有一段時間,我們使用了一種本地ObjectPool<T>實現,它提供了對其內容的阻止訪問。它非常簡單: a Queue<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>。它們似乎都可以正常工作;只是我們最初的實現似乎要快得多。

我的問題:

  1. 為什麼會這樣?可能是因為BlockingCollection<T>提供了更大的靈活性(我知道它通過包裝一個 來工作IProducerConsumerCollection<T>),這必然會帶來性能成本?
  2. 這只是對BlockingCollection<T>課程的完全錯誤的使用嗎?
  3. 如果這是對 的適當使用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>它旨在允許許多生產者執行緒很好地“排隊”數據。這在您的實現中表現不佳,因為鎖定爭用將很快開始成為問題。

話雖如此,我還想在這裡指出一個誤解:

…它可能會提供比我或我的隊友寫的任何東西更好的性能。

這通常是不正確的。框架集合類通常執行得非常好,但對於給定場景,通常不是性能最高的選項。話雖如此,它們往往表現良好,同時非常靈活且非常健壯。它們通常傾向於很好地擴展。“自製”集合類在特定場景中的性能通常優於框架集合,但在用於它們專門設計的場景之外的場景時往往會出現問題。我懷疑這是其中一種情況。

引用自:https://stackoverflow.com/questions/3039724