Pregunta

¿Con qué frecuencia usted se encuentra en realidad el uso de bloqueos de bucle en el código? ¿Qué tan común es encontrarse con una situación en la que el uso de un bucle ocupado en realidad supera el uso de cerraduras?
En lo personal, cuando escribo algún tipo de código que requiere hilo de seguridad, tiendo a punto de referencia con diferentes primitivas de sincronización, y en lo que va, parece que el uso de bloqueos da un mejor rendimiento que el uso de spinlocks. No importa de qué poco tiempo en realidad me sujeto la cerradura, la cantidad de contención de recibo cuando se usa spinlocks es mucho mayor que la cantidad que recibo de uso de bloqueos (por supuesto, corro mis pruebas en una máquina multiprocesador).

Me doy cuenta de que es más probable encontrarse con un spinlock en código de "bajo nivel", pero me interesa saber si lo encuentra útil, incluso en un alto nivel de especie más de la programación?

¿Fue útil?

Solución

Depende de lo que está haciendo. En el código de aplicación general, usted querrá evitar spinlocks.

En las cosas de bajo nivel en el que sólo va a sostener el bloqueo durante un par de instrucciones, y la latencia es importante, una estera spinlock ser una solución mejor que un bloqueo. Pero esos casos son raros, especialmente en el tipo de aplicaciones en las que se utiliza normalmente en C #.

Otros consejos

En C #, "cerraduras de vuelta" han sido, en mi experiencia, casi siempre peor que tomar una cerradura. - Es un hecho poco habitual en bloqueos de giro superan a un bloqueo

Sin embargo, eso no es siempre el caso. .NET 4 es la adición de un System.Threading .SpinLock estructura. Esto proporciona ventajas en situaciones en las que un bloqueo se mantiene durante un tiempo muy corto, y ser agarrado en repetidas ocasiones. A partir de la documentación de MSDN sobre Estructuras de datos para la programación paralela :

  

En los escenarios donde se espera que la espera para el bloqueo a ser corto, SpinLock ofrece un mejor rendimiento que otras formas de bloqueo.

Las cerraduras de vuelta pueden superar a otros mecanismos en los casos en los que estás haciendo algo así como el bloqueo a través de un árbol de bloqueo - si sólo tener cierres de cada nodo para un muy, muy corto período de tiempo, se pueden realizar a cabo una tradicional bloquear. Me encontré con esto en un motor de renderizado con una actualización escena multiproceso, en un momento -. Bloqueos de giro perfilados a cabo para superar el bloqueo con Monitor.Enter

Por mi trabajo en tiempo real, sobre todo con los controladores de dispositivos, he ellos utilizó un poco justo. Resulta que (la última vez que conté esto) a la espera de un objeto de sincronización como un semáforo atado a una alarma de proceso mastica hasta al menos 20 microsegundos, no importa el tiempo que realmente se necesita para que se produzca la interrupción. Un solo cheque de un registro de hardware asignado a la memoria, seguido de un cheque para RDTSC (para permitir un tiempo de espera para que no se bloqueen la máquina) está en el rango de alta nannosecond (básicamente en el ruido). Para la interconexión a nivel de hardware que no debe tomar mucho tiempo en todo, es muy difícil de superar un spinlock.

Mi 2c: Si sus actualizaciones satisfacen algunos criterios de acceso a continuación, son buenos candidatos spinlock:

  • rápido , es decir, usted tendrá tiempo para adquirir el spinlock, realice los cambios y liberar el spinlock en una sola cuantos de hilo para que usted no consigue anticiparse a los mismos mientras se mantiene el spinlock
  • localizada todos los datos que están en la actualización de preferencia una sola página que ya está cargado, no quiere un TLB mientras se mantiene el spinlock, y que definitivamente no quieren un intercambio de error de página leer!
  • atómica que no es necesario ningún otro bloqueo para realizar la operación, es decir. Nunca esperar a que las cerraduras bajo spinlock.

Para cualquier cosa que tenga potencial para producir, se debe utilizar una estructura de bloqueo notificado (eventos, mutex, semáforos, etc.).

Un caso de uso de bloqueos de giro es si espera muy bajo de contención, pero va a tener un montón de ellos. Si no necesita soporte para bloqueo recursivo, un spinlock se puede implementar en un solo byte, y si la contención es muy baja, entonces los residuos ciclo de la CPU es insignificante.

En un caso práctico uso, que a menudo tienen matrices de miles de elementos, donde los cambios a diferentes elementos de la matriz pueden ocurrir de forma segura en paralelo. Las probabilidades de dos hilos intentan actualizar el mismo elemento al mismo tiempo son muy pequeñas (de menos de contención) pero necesito uno para cada elemento de bloqueo (Voy a tener un montón de ellos). En estos casos, por lo general asignar una matriz de uBytes del mismo tamaño que la matriz que estoy actualizando de forma paralela e implementar spinlocks en línea como (en el lenguaje de programación D):

while(!atomicCasUbyte(spinLocks[i], 0, 1)) {}
    myArray[i] = newVal;
atomicSetUbyte(spinLocks[i], 0);

Por otro lado, si tuviera que utilizar bloqueos regulares, que tendría que asignar una matriz de punteros a objetos, y luego asignar un objeto mutex para cada elemento de esta matriz. En los escenarios tales como la descrita anteriormente, esto es simplemente un desperdicio.

Si tiene código crítico rendimiento y que haya determinado que tiene que ser más rápido de lo que actualmente es y que haya determinado que el factor crítico es la velocidad de bloqueo, a continuación, que sería una buena idea para tratar un spinlock. En otros casos, ¿por qué preocuparse? cerraduras normales son más fáciles de usar correctamente.

Tenga en cuenta los siguientes puntos:

  1. La mayoría de las implementaciones de mutexe girar un poco de tiempo antes de que el hilo es en realidad no programada. Debido a esto, es difícil comparar las tesis mutex con spinlocks puros.

  2. Varios hilos SPINING "tan rápido como sea posible" en el mismo spinlock se consomé todo el ancho de banda y drasticly disminuir su eficiencia del programa. Es necesario añadir pequeño tiempo de "dormir" añadiendo NOOP en su bucle de centrifugado.

Casi nunca es necesario utilizar spinlocks en el código de aplicación, en todo caso usted debe evitar.

No puedo cosa de cualquier razón para usar un spinlock en c # código que se ejecuta en un sistema operativo normal. cerraduras ocupados son en su mayoría una pérdida en el nivel de aplicación - el giro puede hacer que se utilice toda la porción de tiempo de CPU, frente a un bloqueo será inmediatamente provocar un cambio de contexto si es necesario.

High código de actuación donde se tiene nr de hilos = nr de procesadores / núcleos podrían beneficiarse en algunos casos, pero si usted necesita la optimización del rendimiento en ese nivel de su probable haciendo juego siguiente generación 3D, trabajando en un sistema operativo embebido con primitivas de sincronización pobres , la creación de un sistema OS / conductor o en todo caso no utilizar C #.

He utilizado bloqueos de giro para la fase de parada-el-mundo del recolector de basura en mi HLVM proyecto porque son fáciles y que es una máquina virtual de juguete. Sin embargo, bloqueos de giro puede ser contraproducente en ese contexto:

Uno de los errores en Potencia del recolector de basura de la Haskell Compiler Glasgow es tan molesto que no tiene un nombre, el " núcleo última desaceleración ". Esto es una consecuencia directa de su uso inadecuado de spinlocks en su GC y se excacerbated en Linux debido a su programador, pero, de hecho, el efecto se puede observar cuando otros programas están compitiendo por tiempo de CPU.

El efecto es claro en el segundo gráfico aquí y se puede ver que afecta a algo más que el último núcleo aquí , donde el programa Haskell ve degradación del rendimiento más allá de sólo 5 núcleos.

Siempre mantenga estos puntos en su mente durante el uso de spinlocks

  • ejecución modo de usuario rápida.
  • Sincroniza hilos dentro de un solo proceso, o procesos múltiples si en la memoria compartida.
  • No se restablece hasta que es propiedad del objeto.
  • No es compatible con la recursividad.
  • consume el 100% de la CPU, mientras que "de espera".

Yo personalmente he visto tantos puntos muertos sólo porque alguien pensó que será una buena idea utilizar spinlock.

Tenga mucho cuidado al utilizar spinlocks

(No puedo enfatizar esto lo suficiente).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top