Queue.Synchronized быстрее, чем с помощью Lock ()? - PullRequest
7 голосов
/ 27 января 2011

У меня есть очередь, в которой операция Enqueue будет выполняться одним потоком, а Dequeue будет выполняться другим.Само собой разумеется, я должен был реализовать некоторую безопасность потока для этого.

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

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

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

Ответы [ 3 ]

17 голосов
/ 27 января 2011

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

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

Обновление

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

Обновление 2

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

«Queue.Synchronized» медленнее, чем «Queue + lock»

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

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

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);
}
6 голосов
/ 27 января 2011

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

Тем не менее, вы, вероятно, просто должны использовать ConcurrentQueue.

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