¿Las implementaciones entrelazadas basadas en CompareExchange deben usar SpinWait?


8

A continuación se muestra una implementación de un método entrelazado basado en Interlocked.CompareExchange.

¿Es aconsejable que este código use un SpinWaitgiro antes de reiterar?

public static bool AddIfLessThan(ref int location, int value, int comparison)
{
    int currentValue;
    do
    {
        currentValue = location; // Read the current value
        if (currentValue >= comparison) return false; // If "less than comparison" is NOT satisfied, return false
    }
    // Set to currentValue+value, iff still on currentValue; reiterate if not assigned
    while (Interlocked.CompareExchange(ref location, currentValue + value, currentValue) != currentValue);
    return true; // Assigned, so return true
}

Lo he visto SpinWaitusado en este escenario, pero mi teoría es que debería ser innecesario. Después de todo, el bucle solo contiene un puñado de instrucciones, y siempre hay un hilo que avanza.

Digamos que dos hilos están compitiendo para realizar este método, y el primer hilo tiene éxito de inmediato, mientras que el segundo hilo inicialmente no hace ningún cambio y tiene que reiterar. Sin otros contendientes, ¿es posible que el segundo hilo falle en su segundo intento ?

Si el segundo hilo del ejemplo no puede fallar en el segundo intento, ¿qué podríamos ganar con un SpinWait? ¿Afeitarse algunos ciclos en el improbable caso de que cientos de hilos compitan para realizar el método?


3
@MindSwipe OP no pregunta si es necesario usar Interlocked; más bien, ¿debería usar SpinWait en lugar de solo el cuerpo del bucle vacío?
Matthew Watson el

1
Probablemente deberías usar solo SpinOncepara evitar que un sistema operativo de un solo subproceso se vea potencialmente hambriento. Ver stackoverflow.com/questions/37799381/…
Matthew Watson el

1
@MindSwipe La condición de carrera ya se maneja correctamente en virtud del uso Interlocked. Para aclarar, solo me interesa saber si a SpinWaittiene sentido o no , por ejemplo, para ahorrar significativamente los ciclos de la CPU o (¡gracias @MatthewWatson!) Evitar que un sistema operativo de un solo subproceso se muera de hambre.
Timo

1
@AloisKraus Entiendo eso en teoría, pero mi razonamiento es que este ciclo tiene éxito en el próximo intento , independientemente de si esperamos o no. Es por eso que espero que SpinWaiteso ni siquiera reduzca el consumo de energía, ya que de todos modos se realizará el mismo número de intentos. (Con dos hilos, ese es un intento para el primero y dos para el segundo hilo.)
Timo

1
@Timo, creo que puede beneficiarse de un bloqueo de giro en plataformas donde Interlocked no es compatible con el hardware, pero debe ser emulado.
Nick

Respuestas:


2

Mi opinión no experta es que en este caso particular, donde dos hilos ocasionalmente llaman AddIfLessThan, a SpinWaites innecesario. Podría ser beneficioso en el caso de que los dos hilos estuvieran llamando AddIfLessThanen un bucle cerrado, de modo que cada hilo pudiera avanzar sin interrupciones durante algunos μseg.

En realidad hice un experimento y medí el rendimiento de una llamada de hilo AddIfLessThan en un bucle cerrado frente a dos hilos. Los dos hilos necesitan casi cuatro veces más para hacer la misma cantidad de bucles (acumulativamente). Agregar SpinWaita la mezcla hace que los dos hilos sean solo un poco más lentos que el hilo único.


1
¿Qué efecto tiene SpinWait con un hilo? Por ejemplo, ¿hay alguna razón para no usarlo?
Ian Ringrose el

2
@IanRingrose lo hace un 15% más lento. Declaro una variable de tipo SpinWaitdentro del AddIfLessThanmétodo, y aunque es un tipo de valor y su SpinOncemétodo nunca se llama, todavía agrega algo de sobrecarga.
Theodor Zoulias el

3
@TheodorZoulias: Puede declarar en su cuerpo de IL que omita el inicio de los locales, lo que debería devolverle su rendimiento. Necesita emitir el código IL para esto porque todavía no hay soporte de idioma. Ver github.com/dotnet/csharplang/blob/master/proposals/... Cuando desee giro una vez que se puede llamar de reinicio en él y luego SpinOnce.
Alois Kraus

1
Gracias @TheodorZoulias! FYI, los dos hilos fueron solo un ejemplo para ilustrar mi punto. Esta es una biblioteca de clases y se puede usar según lo considere conveniente el consumidor. Lo mejor que podemos hacer es hacer suposiciones sobre la probabilidad de condiciones de carrera con muchos hilos. Dado el poco tiempo que lleva la operación, considero poco probable que muchos hilos realicen esta operación solapando entre sí.
Timo

1
Si está actualizando un ciclo cerrado, probablemente debería considerar hacer que el valor sea privado y actualizar una vez al final del ciclo. Si el valor compartido necesita mantenerse más o menos real y el ciclo es extremadamente largo, considere actualizar el valor como si avanzara una barra de progreso, una vez cada X ms.
curioso

2

Dos hilos no es un tema de SpinWaitdiscusión. Pero este código no nos dice cuántos hilos pueden realmente competir por el recurso y con un número relativamente alto de hilos SpinWaitpuede ser beneficioso. En particular, con un mayor número de subprocesos, la cola virtual de subprocesos, que intentan adquirir con éxito el recurso, se alarga y los subprocesos que finalmente se atienden tienen buenas posibilidades de exceder su intervalo de tiempo asignado por el planificador, que a su vez puede conducir a un mayor consumo de CPU y puede afectar la ejecución de otros subprocesos programados incluso con mayor prioridad. losSpinWaittiene una buena respuesta a esta situación estableciendo un límite superior de giros permitidos después de lo cual se va a ejecutar el cambio de contexto. Por lo tanto, es una compensación razonable entre la necesidad de realizar una costosa llamada al sistema para activar un cambio de contexto y un consumo de CPU en modo de usuario no controlado, que corre el riesgo de afectar la ejecución de otros hilos en ciertas situaciones.


Los dos hilos fueron solo un ejemplo para ilustrar mi punto. Esta es una biblioteca de clase. Deberíamos poder abordar cualquier escenario realista, teniendo en cuenta su probabilidad. Dado el poco tiempo que lleva la operación, considero poco probable que muchos hilos realicen esta operación solapando entre sí.
Timo

Haces un punto justo. Estoy de acuerdo en que, con muchos hilos, algunos fallarían muchos antes de tener éxito. Sin embargo, una parte de mí piensa que la operación es tan pequeña que, por ejemplo, realizarla 100 veces sigue siendo un maní. Con esto en mente, ¿esperarías que sea una SpinWaitexageración o no?
Timo

2
@Timo, la única respuesta es la medición, especialmente teniendo en cuenta que este será un código de biblioteca que debería ser capaz de manejar varios escenarios de carga y hardware, supongo. Para una mayor imparcialidad y, por lo tanto, para un mejor rendimiento, usar la SpinWaitestrategia es mejor (con un costo relativamente pequeño de docenas de microsegundos) que no usar eso a menos que excluya de antemano la posibilidad de escenarios de alta carga baja, por lo que encontrar un punto de carga en el que el rendimiento pueda estar afectado le daría una pista de si lo necesita o no (tenga en cuenta que también puede depender de la cantidad de núcleos).
Dmytro Mukalov

Con tantos hilos con la necesidad de un recurso común durante tanto tiempo con tanta frecuencia que uno va a consumir su segmento de tiempo, consideraría hacer copias privadas del recurso (para sincronizar más adelante) o incluso abandonar los hilos.
curioso

@curiousguy Como se indicó en 4 comentarios, esta es una biblioteca de clases. Por supuesto, el código consumidor podría usar un enfoque de caso específico. El objetivo de la biblioteca de clases, sin embargo, es ser resistente a varios casos de uso. Es por eso que la pregunta permanece en qué situaciones SpinWaitagrega valor. Hasta ahora, esos parecen ser (A) el caso de un solo núcleo, y (B) posiblemente el caso de contención muy alta.
Timo
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.