Некоторое время в моей компании мы использовали собственной разработки ObjectPool
реализация, которая обеспечивает блокирующий доступ к его содержанию. Это довольно просто: a Queue
, object
соединяться, и 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. Но недавно мы обнаружили, что, так как мы используем Реактивные Расширения, мы на самом деле имеем Concurrent
пространство имен, доступное нам (в System.Threading.dll).
Естественно, я полагал это с тех пор BlockingCollection
один из базовых классов в Concurrent
пространство имен, это, вероятно, предложило бы лучшую производительность, чем что-нибудь, что я или мои товарищи по команде записали.
Таким образом, я пытался писать новую реализацию, которая работает очень просто:
public T Borrow() {
return _blockingCollection.Take();
}
public void Return(T obj) {
_blockingCollection.Add(obj);
}
К моему удивлению, согласно некоторым простым тестам (одалживающий/возвращающий к пулу несколько тысяч раз от нескольких потоков), наша исходная реализация значительно бьется BlockingCollection
с точки зрения производительности. Они оба, кажется, работают правильно; это просто, что наша исходная реализация, кажется, намного быстрее.
Мой вопрос:
BlockingCollection
предлагает большую гибкость (я понимаю, что она работает путем обертывания IProducerConsumerCollection
), который обязательно представляет производительность наверху?BlockingCollection
класс?BlockingCollection
, разве я просто не использую правильно? Например, Take
/Add
чрезмерно упрощенный подход, и существует далекий лучше работающий способ получить ту же функциональность?Если у любого нет некоторого понимания для предложения в ответ на тот третий вопрос, похоже, что мы будем придерживаться нашей исходной реализации на данный момент.
Здесь есть несколько потенциальных возможностей.
Во-первых, BlockingCollection
в Reactive Extensions - это резервный порт, а не совсем то же самое, что и в окончательной версии .NET 4. Я не удивлюсь, если производительность этого бэкпорта будет отличаться от .NET 4 RTM (хотя я специально не профилировал эту коллекцию). Большая часть TPL работает в .NET 4 лучше, чем в backport .NET 3.5.
При этом я подозреваю, что ваша реализация будет превосходить BlockingCollection
, если у вас есть один поток-производитель и один поток-потребитель. С одним производителем и одним потребителем ваша блокировка будет иметь меньшее влияние на общую производительность, а событие сброса - очень эффективное средство ожидания на стороне потребителя.
Однако BlockingCollection
разработан, чтобы позволить многим потокам-производителям очень хорошо «ставить в очередь» данные. Это не будет хорошо работать с вашей реализацией, так как конкуренция за блокировку довольно быстро начнет становиться проблематичной.
При этом я также хотел бы указать здесь на одно заблуждение:
... он, вероятно, будет предлагать лучшую производительность, чем все, что написано мной или моими товарищами по команде.
Это часто неправда. Классы коллекции каркаса обычно работают очень хорошо , но часто не являются наиболее производительным вариантом для данного сценария. При этом они, как правило, работают хорошо, будучи очень гибкими и надежными. Часто они очень хорошо масштабируются.«Самодельные» классы коллекций часто превосходят по эффективности коллекции каркаса в определенных сценариях, но имеют тенденцию вызывать проблемы при использовании в сценариях, отличных от того, для которого они были специально разработаны. Я подозреваю, что это одна из таких ситуаций.