2011-01-27 10 views
7

Tengo una cola en la que la operación Enqueue sería realizada por un hilo y Dequeue sería ejecutada por otro. Huelga decir que tuve que implementar algo de seguridad de hilo para ello.es Queue.Synchronized más rápido que usar un bloqueo()?

Intenté por primera vez utilizar un bloqueo en la cola antes de cada Enqueue/Dequeue, ya que proporciona un mejor control del mecanismo de bloqueo. Funcionó bien, pero mi mente curiosa me llevó a probar un poco más.

Intenté utilizar el envoltorio Queue.Synchronized manteniendo todo lo demás igual. Ahora, no estoy seguro de si es cierto, pero el rendimiento parece un poco más rápido con este enfoque.

¿Crees que en realidad hay alguna diferencia en el rendimiento entre los dos, o solo estoy imaginando cosas aquí ...? :)

+3

Simplemente sacando una cerradura con cualquiera de los métodos es increíblemente rápido. El golpe de rendimiento proviene de cuánta contención hay para los bloqueos. – serg10

Respuesta

16

Al solicitar Queue.Synchonized se obtiene una SynchronizedQueue a cambio que utiliza un lock mínimamente alrededor llama a Enqueue y Dequeue en una cola interna. Por lo tanto, el rendimiento debe ser el mismo que usar un Queue y administrar el bloqueo usted mismo para Enqueue y Dequeue con su propio lock.

De hecho, estás imaginando cosas, deberían ser lo mismo.

actualización

No es en realidad el hecho de que cuando se utiliza un SynchronizedQueue va a añadir una capa de direccionamiento indirecto ya que tienes que ir a través de los métodos de contenedor para llegar a la cola interna que esté administrando. En todo caso, esto debería ralentizar las cosas muy poco, ya que tienes un marco extra en la pila que necesita ser administrado para cada llamada. Sin embargo, Dios sabe si el forro interior cancela esto. Lo que sea - es mínimo.

Actualización 2

ahora han referenciado esto, y como se predijo en mi anterior actualización

"Queue.Synchronized" es más lenta que la "cola + lock"

He llevado a cabo una prueba de un solo hilo ya que ambos usan la misma técnica de bloqueo (es decir, lock) por lo que probar la sobrecarga pura en una "línea recta" parece razonable.

Mi referencia produjo los siguientes resultados para un lanzamiento construcción:

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 

usando el siguiente código:

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

No si el OP no es suficientemente agresivo para liberar el bloqueo, o si está demasiado ansioso por adquirirlo. – jason

+0

@Jason Estoy asumiendo que el OP está bloqueando mínimamente las llamadas 'Enqueue' y' Dequeue', pero veo su punto. He aclarado un poco en mi respuesta. –

+0

gracias por invertir tiempo y esfuerzo en obtener una respuesta autoritaria a este problema. Felicitaciones a usted. –

6

No podemos responder esto por usted. Solo usted puede responderlo usted mismo obteniendo un generador de perfiles y probando ambos escenarios (Queue.Synchronized contra Lock) en datos reales de su aplicación. Puede que ni siquiera sea un cuello de botella en su aplicación.

Dicho esto, probablemente debería utilizar ConcurrentQueue.

+0

Lamentablemente, todavía estamos en .net 3.5 .. –

+1

@ Danish: echa un vistazo a [Rx] (http://msdn.microsoft.com/en-us/devlabs/ee794896). Tiene un respaldo de 'ConcurrentQueue ' para .NET 3.5. –

+0

@Dan ¿Es esa versión de 'ConcurrentQueue ' más rápida en general? He visto ese consejo algunas veces, pero no hay cifras de rendimiento real. ¿Alguna cifra? –

0
  1. Queue.Synchronize Wraps una nueva cola sincronizada mientras el bloqueo de cola. SyncRoot le da a un Objeto acceso a la cola sincronizada para que así pueda garantizar la Seguridad de Subprocesos en la Cola mientras usa las operaciones En cola y Dequeue simult al instante usando hilos.
Cuestiones relacionadas