Queue.Synchronized быстрее, чем с помощью Lock()?
У меня есть очередь, в которой операция Enqueue будет выполняться одним потоком, а Dequeue будет выполняться другим. Само собой разумеется, я должен был реализовать некоторую безопасность потока для этого.
Сначала я пытался использовать блокировку в очереди перед каждой постановкой / снятием очереди, поскольку это дает лучший контроль над механизмом блокировки. Это сработало хорошо, но мой любопытный ум заставил меня проверить еще.
Затем я попытался использовать Queue.Synchronized wrapper, оставив все остальное таким же. Теперь я не уверен, правда ли это, но при таком подходе производительность кажется немного выше.
Как вы думаете, на самом деле есть какая-то разница в производительности между ними, или я просто воображаю вещи здесь?:)
3 ответа
При запросе Queue.Synchonized
Вы получаете SynchronizedQueue
взамен который использует lock
очень минимально вокруг звонков Enqueue
а также Dequeue
на внутренней очереди. Поэтому производительность должна быть такой же, как при использовании Queue
и управлять блокировкой для Enqueue
а также Dequeue
с вашим собственным lock
,
Вы действительно воображаете вещи - они должны быть одинаковыми.
Обновить
На самом деле существует тот факт, что при использовании SynchronizedQueue
вы добавляете слой косвенности, поскольку вам нужно пройти через методы-оболочки, чтобы попасть во внутреннюю очередь, которой он управляет. Во всяком случае, это должно очень замедлить процесс, поскольку у вас есть дополнительный кадр в стеке, которым нужно управлять для каждого вызова. Бог знает, отменяет ли это встраивание, хотя. Что угодно - это минимально.
Обновление 2
Теперь я проверил это, и как и предсказывалось в моем предыдущем обновлении:
"Queue.Synchronized" медленнее, чем "Queue+lock"
Я провел однопоточный тест, так как они оба используют одну и ту же технику блокировки (т.е. 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);
}
Мы не можем ответить на это для вас. Только вы можете ответить на этот вопрос самостоятельно, получив профилировщик и протестировав оба сценария (Queue.Synchronized
против Lock
) на реальных данных из вашего приложения. Это может даже не быть узким местом в вашем приложении.
Тем не менее, вы должны, вероятно, просто использовать ConcurrentQueue
,
- Queue.Synchronize Обертывает новую очередь Synchronized, в то время как Lock Queue.SyncRoot предоставляет объекту доступ к очереди синхронизированным способом, так что таким образом вы можете обеспечить безопасность потока в очереди, одновременно используя операции Enqueue и Dequeue, используя потоки.