2009-05-12 9 views
21

Estaba depurando una aplicación de subprocesos múltiples y encontré la estructura interna de CRITICAL_SECTION. Encontré el miembro de datos LockSemaphore de CRITICAL_SECTION uno interesante.¿La sección crítica es siempre más rápida?

Parece que LockSemaphore es un evento de restablecimiento automático (no un semáforo como su nombre indica) y el sistema operativo crea este evento en silencio cuando por primera vez un hilo espera en Critcal Section que está bloqueado por algún otro hilo.

Ahora, me pregunto ¿la sección crítica es siempre más rápida? El evento es un objeto del kernel y cada objeto de la sección Critical está asociado con el objeto del evento, ¿cómo puede ser más rápido el Critical Section en comparación con otros objetos kernel como Mutex? Además, ¿cómo afecta el objeto de evento interno el rendimiento de la sección Crítica?

Aquí es la estructura de la CRITICAL_SECTION:

struct RTL_CRITICAL_SECTION 
{ 
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo; 
    LONG LockCount; 
    LONG RecursionCount; 
    HANDLE OwningThread; 
    HANDLE LockSemaphore; 
    ULONG_PTR SpinCount; 
}; 
+2

Recuerde también que los detalles de implementación de CriticalSection pueden variar de una versión a otra del sistema operativo. Puede ser instructivo observar estos detalles, pero no confíe en ellos, ya que pueden cambiar. –

+0

Sin duda es un semáforo en NT5 – paulm

Respuesta

34

Cuando dicen que una sección crítica es "rápida", significan "es barato adquirir una cuando todavía no está bloqueada por otra cadena".

[Tenga en cuenta que si es que ya está bloqueado por otro hilo, entonces no importa tanto lo rápido que es.]

La razón por la que es tan rápido es porque, antes de entrar en el núcleo , usa el equivalente de InterlockedIncrement en uno de esos campos LONG (quizás en el campo LockCount) y si tiene éxito, considera el bloqueo adquirido sin haber ingresado en el núcleo.

Creo que la API InterlockedIncrement se implementó en el modo de usuario como un código de operación "LOCK INC" ... en otras palabras, puede adquirir una sección crítica sin hacer ninguna transición de anillo al kernel.

+1

+1 Buena explicación. De hecho, puede probarse usted mismo si puede adquirir la sección crítica leyendo LockCount. – Arno

+0

@Arno: hay ['TryEnterCriticalSection'] (https://msdn.microsoft.com/en-us/library/windows/desktop/ms686857%28v=vs.85%29.aspx) para eso. – rwong

3

Los CriticalSections harán girar un corto tiempo (algunos ms) y mantener el control de si el bloqueo está libre. Después de que el recuento de centrifugados agote el tiempo de espera, volverá al evento kernel. Entonces, en el caso en que el titular de la cerradura salga rápidamente, nunca tendrá que realizar la costosa transición al código kernel.

EDIT: fuimos y encontramos algunos comentarios en mi código: al parecer el Montón Director de MS utiliza una cuenta de giro de 4000 (incrementos enteros, no ms)

+1

¿No es 4 segundos demasiado poco? Además, el objeto de evento se crea en el momento en que el sistema operativo encuentra que la sección crítica está bloqueada. está un poco por encima de la cabeza, ¿verdad? –

+0

wow, 4 segundos es enorme en términos informáticos ... ¿quizás quiso decir 4000 microsegundos (es decir, 4 ms)? No creo que un cambio de contexto tarde 4ms en una máquina moderna. ¿Puede proporcionar una cita de la figura 4sec? – rmeador

+4

Actualmente, el valor de conteo de vueltas es solo un conteo, no es un valor expresado en unidades de tiempo. El valor de 4.000 proviene de la documentación de MSDN para InitializeCriticalSectionAndSpinCount() Tenga en cuenta que esto puede cambiar de una versión a otra. Los documentos dicen 'aproximadamente 4,000'. – Foredecker

25

En rendimiento en el trabajo, algunas cosas entran en la categoría "siempre" :) Si implementa algo usted mismo que es similar a una sección crítica del sistema operativo utilizando otras primitivas, entonces las probabilidades son que serán más lentas en la mayoría de los casos.

La mejor forma de responder a su pregunta es midiendo el rendimiento. El rendimiento de los objetos de SO es muy según el escenario. Por ejemplo, las secciones críticas son generalmente consideradas "rápidas" si la contención es baja. También se consideran rápidos si el tiempo de bloqueo es menor que el tiempo de conteo de vueltas.

Lo más importante para determinar es si la contención en una sección crítica es el factor limitante de primer orden en su aplicación. De lo contrario, simplemente use una sección crítica de manera normal y trabaje en el cuello de botella (o cuellos) principal de sus aplicaciones.

Si el rendimiento crítico de la sección es crítico, entonces puede considerar lo siguiente.

  1. Ajuste con cuidado el recuento de bloqueo de centrifugado para sus secciones críticas "calientes". Si el rendimiento es primordial, entonces el trabajo aquí lo vale. Recuerde, mientras que el bloqueo de giro evita el modo de usuario para la transición del kernel, consume tiempo de CPU a un ritmo vertiginoso: mientras gira, nada más puede usar ese tiempo de CPU. Si se mantiene un bloqueo durante el tiempo suficiente, entonces el hilo giratorio bloqueará realmente, liberando esa CPU para hacer otro trabajo.
  2. Si tiene un patrón de lector/escritor, entonces considere usar el Slim Reader/Writer (SRW) locks. La desventaja aquí es que solo están disponibles en Vista y Windows Server 2008 y productos posteriores.
  3. Puede usar condition variables con su sección crítica para minimizar el sondeo y la contención, y los hilos emergentes solo cuando sea necesario. Nuevamente, estos son compatibles con Vista y Windows Server 2008 y productos posteriores.
  4. Considere usar Interlocked Singly Linked Lists (SLIST) - estos son eficientes y 'sin bloqueo'. Aún mejor, son compatibles con XP y Windows Server 2003 y productos posteriores.
  5. Examine su código: es posible que pueda romper un bloqueo 'caliente' mediante la refacturación de algún código y mediante una operación de enclavamiento, o SLIST para sincronización y comunicación.

En resumen, los escenarios de ajuste que tienen conflicto de bloqueo pueden ser un trabajo desafiante (¡pero interesante!). Concéntrese en medir el rendimiento de sus aplicaciones y comprender dónde están sus rutas calientes. Las herramientas xperf en el Windows Performance Tool kit son tus amigos aquí :) Acabamos de lanzar la versión 4.5 en el SDK de Microsoft Windows para Windows 7 y .NET Framework 3.5 SP1 (ISO is here, web installer here). Puede encontrar el foro para las herramientas xperf here. V4.5 es totalmente compatible con Win7, Vista, Windows Server 2008 - todas las versiones.

4

CriticalSections es más rápido, pero InterlockedIncrement/InterlockedDecrement es más. Consulte esta muestra de uso de implementación LightweightLock full copy.

+0

Su vínculo está muerto, es posible que desee editar su respuesta – Moe

+1

LightWeightLock puede ser más lento ya que es un spinlock. También puede bloquearse en caso de inversión de prioridad. –

+0

Tiendo a usar las funciones enclavadas (más específicamente InterlockedCompareExchange) para proteger pequeños bloques de código que no llaman a otras funciones. En mi caso noté que las funciones de Enclavamiento eran dos veces más rápidas que las funciones de Sección Crítica (lo cual era muy importante en mi caso, ya que era un bloque pequeño que a menudo se ejecutaba). La desventaja de las funciones enclavadas es que no puedes anidarlas y puedes utilizarlas de forma accidental (por ejemplo, bloquear un hilo, desbloquearlo en otro hilo). – Patrick

1

Aquí está una manera de verlo:

Si no hay contención, entonces el bloqueo de bucle es muy rápido en comparación con ir al modo kernel para un objeto mutex.

Cuando hay una disputa, una sección crítica es un poco más costosa que usar un Mutex directamente (debido al trabajo adicional para detectar el estado de bloqueo).

Por lo tanto, se reduce a un promedio ponderado, donde los pesos dependen de las características específicas de su patrón de llamadas. Dicho eso, si tienes poca opinión, entonces una CriticalSection es una gran victoria. Si, por otro lado, constantemente tienes muchas disputas, entonces estarías pagando una pequeña penalización por usar un Mutex directamente. Pero en ese caso, lo que ganarías al cambiar a un Mutex es pequeño, por lo que probablemente sería mejor que intentaras reducir la contienda.

+0

Establezca el SpinCount en 0 y al menos nunca es más lento que un mutex incluso bajo fuerte contención. – Lothar

+0

¡Jaja, y es lo mismo que un mutex! –

1

La sección crítica es más rápida que mutex, porque la sección crítica no es un objeto kernel. Esto es parte de la memoria global del proceso actual. Mutex en realidad reside en Kernel y la creación del objeto mutext requiere un cambio de núcleo, pero en el caso de la sección crítica no. Aunque la sección crítica es rápida, habrá un cambio de kernel al usar la sección crítica cuando los hilos vayan a esperar el estado. Esto se debe a que la programación de hilos ocurre en el lado del kernel.

Cuestiones relacionadas