2011-01-27 3 views
7

У меня есть очередь, в которой операция Enqueue будет выполняться одним потоком, а Dequeue будет выполняться другим. Излишне говорить, что мне пришлось реализовать некоторую безопасность потока.- это Queue.Synchronized быстрее, чем использование Lock()?

Сначала я попытался использовать блокировку в очереди перед каждым Enqueue/Dequeue, поскольку он дает лучший контроль за механизмом блокировки. Это сработало хорошо, но мой любопытный ум заставил меня проверить еще кое-что.

Затем я попытался использовать Queue.Synchronized wrapper, сохраняя все остальное одинаково. Теперь я не уверен, что это правда, но производительность с этим подходом выглядит немного быстрее.

Как вы думаете, на самом деле есть какая-то разница в производительности между двумя, или я просто представляю здесь вещи ...? :)

+3

Просто вынимая замок с помощью любого из методов, это безумно быстро. Выигрыш производительности зависит от того, сколько споров для замков. – serg10

ответ

16

Queue.Synchonized При запросе вы получите SynchronizedQueue в ответ, который использует lock очень минимально вокруг вызывает к Enqueue и Dequeue на внутренней очереди. Таким образом, производительность должна быть такой же, как с использованием Queue и управления блокировкой для Enqueue и Dequeue с вашим собственным lock.

Вы действительно представляете вещи - они должны быть одинаковыми.

Update

Существует на самом деле тот факт, что при использовании SynchronizedQueue вы добавляете слой косвенности, как вы должны пройти через методу обертки, чтобы добраться до внутренней очереди которой он управляет. Если что-либо, это должно замедлить работу до минимума, поскольку у вас есть дополнительный фрейм в стеке, который нужно управлять для каждого вызова. Бог знает, что если подкладка не отменяет этого. Неважно - это минимальный.

Update 2

Я теперь протестированные это, и, как предсказано в моем предыдущем обновлении:

"Queue.Synchronized" медленнее, чем "Очередь + блокировка"

Я выполнил однопоточный тест, поскольку оба они используют одну и ту же технологию блокировки (то есть lock), поэтому тестирование чистых накладных расходов на «прямой линии» представляется разумным.

Мой тест дает следующие результаты для выпуска сборки:

Iterations  :10,000,000 

Queue+Lock  :539.14ms 
Queue+Lock  :540.55ms 
Queue+Lock  :539.46ms 
Queue+Lock  :540.46ms 
Queue+Lock  :539.75ms 
SynchonizedQueue:578.67ms 
SynchonizedQueue:585.04ms 
SynchonizedQueue:580.22ms 
SynchonizedQueue:578.35ms 
SynchonizedQueue:578.57ms 

Используя следующий код:

private readonly object _syncObj = new object(); 

[Test] 
public object measure_queue_locking_performance() 
{ 
    const int TestIterations = 5; 
    const int Iterations = (10 * 1000 * 1000); 

    Action<string, Action> time = (name, test) => 
    { 
     for (int i = 0; i < TestIterations; i++) 
     { 
      TimeSpan elapsed = TimeTest(test, Iterations); 
      Console.WriteLine("{0}:{1:F2}ms", name, elapsed.TotalMilliseconds); 
     } 
    }; 

    object itemOut, itemIn = new object(); 
    Queue queue = new Queue(); 
    Queue syncQueue = Queue.Synchronized(queue); 

    Action test1 =() => 
    { 
     lock (_syncObj) queue.Enqueue(itemIn); 
     lock (_syncObj) itemOut = queue.Dequeue(); 
    }; 

    Action test2 =() => 
    { 
     syncQueue.Enqueue(itemIn); 
     itemOut = syncQueue.Dequeue(); 
    }; 

    Console.WriteLine("Iterations:{0:0,0}\r\n", Iterations); 
    time("Queue+Lock", test1); 
    time("SynchonizedQueue", test2); 

    return itemOut; 
} 

[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")] 
private static TimeSpan TimeTest(Action action, int iterations) 
{ 
    Action gc =() => 
    { 
     GC.Collect(); 
     GC.WaitForFullGCComplete(); 
    }; 

    Action empty =() => { }; 

    Stopwatch stopwatch1 = Stopwatch.StartNew(); 

    for (int j = 0; j < iterations; j++) 
    { 
     empty(); 
    } 

    TimeSpan loopElapsed = stopwatch1.Elapsed; 

    gc(); 
    action(); //JIT 
    action(); //Optimize 

    Stopwatch stopwatch2 = Stopwatch.StartNew(); 

    for (int j = 0; j < iterations; j++) action(); 

    gc(); 

    TimeSpan testElapsed = stopwatch2.Elapsed; 

    return (testElapsed - loopElapsed); 
} 
+0

Не, если ОП недостаточно агрессивно освобождает замок или слишком охотно приобретает его. – jason

+0

@Jason Я делаю предположение, что OP использует блокировку минимально вокруг вызовов «Enqueue» и «Dequeue», но я вижу вашу точку. Я немного уточнил в своем ответе. –

+0

благодарит за инвестирование времени и усилий в получении авторитетного ответа на эту проблему. Престижность к вам. –

6

Мы не можем ответить на этот вопрос. Только вы можете ответить на него сами, получив профилировщик и проверив оба сценария (Queue.Synchronized против Lock) на реальных данных из вашего приложения. Это может даже не быть узким местом в вашем приложении.

Это, скорее всего, вы должны использовать только ConcurrentQueue.

+0

К сожалению, мы по-прежнему находимся на .net 3.5 .. –

+1

@Danish: Отъезд [Rx] (http://msdn.microsoft.com/en-us/devlabs/ee794896). Он имеет backport 'ConcurrentQueue ' для .NET 3.5. –

+0

@ Dan Является ли эта версия 'ConcurrentQueue ' фактически вообще быстрее? Я видел этот совет несколько раз, но никаких фактических показателей эффективности. Любые цифры? –

0
  1. Queue.Synchronize Обертка новой очереди Синхронное во время очереди блокировки. SyncRoot предоставляет объекту доступ к синхронизированной очереди, так что таким образом вы можете обеспечить безопасность потока в очереди при одновременном использовании операций Enqueue и Dequeue с помощью Threads.
Смежные вопросы