BlockingCollection(T)性能 [英] BlockingCollection(T) performance

查看:230
本文介绍了BlockingCollection(T)性能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的公司,我们使用了一个自己的 ObjectPool< T> 实现,它提供了对其内容的阻塞访问。这很简单:队列< T> ,一个对象锁定,一个



类的肉类真的是这两种方法:

p>

  public T Borrow(){
lock(_queueLock){
if(_queue.Count&
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。但最近我们发现,由于我们使用无效扩展,我们实际上

p>当然,我认为,由于 BlockingCollection< T> Concurrent 命名空间中的核心类之一,它可能提供比我或我的队友写的任何更好的性能。 / p>

所以我试着编写一个非常简单的新实现:

  public T Borrow(){
return _blockingCollection.Take();
}

public void Return(T obj){
_blockingCollection.Add(obj);
}



令我感到惊讶的是,根据一些简单的测试(借用/来自多个线程的几千次),我们原来的实现在性能方面显着地节省 BlockingCollection< T> 。他们似乎都正确工作 ;



我的问题:


  1. 为什么会这样?这可能是因为 BlockingCollection< T> 提供了更大的灵活性(我理解它通过包装 IProducerConsumerCollection< T> ),这必然会引入性能开销?

  2. 这只是一个扁平的误导使用 BlockingCollection< T> class?

  3. 如果这是 BlockingCollection& ,我只是没有正确使用?例如, Take / 添加方法过于简单,有一个更好的表现方式,功能?

除非有人对第三个问题有任何见解,否则我们会坚持使用我们的原始

解决方案

这里有几个潜在的可能性。



首先,反应式扩展中的 BlockingCollection< T> 是一个反向端口,并不完全相同。 NET 4最终版本。我不会感到惊讶,如果这个backport的性能不同于.NET 4 RTM(虽然我没有剖析这个集合,具体)。很多TPL在.NET 4中的表现都优于.NET 3.5后端。



这就是说,我怀疑你的实现会优于 BlockingCollection< T> 如果你有一个生产者线程和一个消费者线程。对于一个生产者和一个消费者,你的锁对总体性能的影响会更小,而重置事件是在消费者端等待的非常有效的手段。



但是, BlockingCollection 被设计为允许许多生产者线程非常好地排队数据。这将不能很好地与您的实现,因为锁争用将开始变得相当快的问题。



话虽如此,我也想指出一个误解这里:


...它可能比我或我的队友写的更好的性能。


这通常不是真的。框架集合类通常非常好地执行,但是对于给定情况通常不是最高效的选项。话虽如此,他们往往表现良好,同时非常灵活和非常鲁棒。他们往往规模很大。 自写集合类在特定场景中通常优于框架集合,但是当在特定设计的场景之外的场景中使用时,往往是有问题的。我怀疑这是其中的一种情况。


For a while at my company we've used a home-grown ObjectPool<T> implementation that provides blocking access to its contents. It's pretty straightforward: a Queue<T>, an object to lock on, and an AutoResetEvent to signal to a "borrowing" thread when an item is added.

The meat of the class is really these two methods:

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();
}

We have been using this and a few other collection classes instead of those provided by System.Collections.Concurrent because we are using .NET 3.5, not 4.0. But recently we discovered that since we are using Reactive Extensions, we actually do have the Concurrent namespace available to us (in System.Threading.dll).

Naturally, I figured that since BlockingCollection<T> is one of the core classes in the Concurrent namespace, it would probably offer better performance than anything I or my teammates wrote.

So I tried writing a new implementation that works very simply:

public T Borrow() {
    return _blockingCollection.Take();
}

public void Return(T obj) {
    _blockingCollection.Add(obj);
}

To my surprise, according to some simple tests (borrowing/returning to the pool a few thousand times from multiple threads), our original implementation significantly beats BlockingCollection<T> in terms of performance. They both appear to work correctly; it's just that our original implementation seems to be much faster.

My question:

  1. Why would this be? Is it perhaps because BlockingCollection<T> offers greater flexibility (I understand it works by wrapping an IProducerConsumerCollection<T>), which necessarily introduces performance overhead?
  2. Is this just a flat-out misguided use of the BlockingCollection<T> class?
  3. If this is an appropriate use of BlockingCollection<T>, am I just not using properly? For example, is the Take/Add approach overly simplistic, and there's a far better-performing way to get the same functionality?

Unless anyone has some insight to offer in response to that third question, it looks like we'll be sticking with our original implementation for now.

解决方案

There are a couple of potential possibilities, here.

First, BlockingCollection<T> in the Reactive Extensions is a backport, and not exactly the same as the .NET 4 final version. I wouldn't be surprised if the performance of this backport differs from .NET 4 RTM (though I haven't profiled this collection, specifically). Much of the TPL performs better in .NET 4 than in the .NET 3.5 backport.

That being said, I'd suspect your implementation will out-perform BlockingCollection<T> if you have a single producer thread and a single consumer thread. With one producer and one consumer, your lock is going to have a smaller impact on the total performance, and the reset event is a very effective means of waiting on the consumer side.

However, BlockingCollection<T> is designed to allow many producer threads to "enqueue" data very well. This will not perform well with your implementation, as the locking contention will start to become problematic fairly quickly.

That being said, I'd also like to point out one misconception here:

...it would probably offer better performance than anything I or my teammates wrote.

This is often not true. The framework collection classes typically perform very well, but are often not the most performant option for a given scenario. That being said, they tend to perform well while being very flexible and very robust. They often tend to scale very well. "Home-written" collection classes often outperform framework collections in specific scenarios, but tend to be problematic when used in scenarios outside of the one for which they were specifically designed. I suspect this is one of those situations.

这篇关于BlockingCollection(T)性能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆