c++ performance atomic reference-counting memory-fences

c++ - ¿Es el decremento atómico más caro que el incremento?



performance atomic (3)

En su blog Herb Sutter escribe.

[...] porque el incremento del recuento de referencias del puntero inteligente generalmente se puede optimizar para que sea el mismo que un incremento ordinario en una implementación de shared_ptr optimizada - solo una instrucción de incremento ordinaria, y no vallas, en el código generado.

Sin embargo, el decremento debe ser un decremento atómico o equivalente, que genera instrucciones especiales de memoria del procesador que son más costosas en sí mismas y que, además de eso, provocan restricciones en el límite de memoria para optimizar el código circundante.

El texto trata sobre la implementación de shared_ptr y no estoy seguro de que su comentario se aplique solo a esto o, en general, sea el caso. De su formulación deduzco que es generalmente .

Pero cuando lo pienso solo puedo pensar en un "decremento más caro" cuando sigue inmediatamente un if(counter==0) , lo que probablemente sea el caso con shared_ptr .

Por lo tanto, me pregunto si el ++counter la operación atómica ++counter es (generalmente) siempre más rápido que --counter , o simplemente porque se usa if(--counter==0)... with shared_ptr ?


Creo que se refiere al hecho de que el incremento puede ser "oculto", donde el "decremento y verificación" debe realizarse como una sola operación.

No conozco ninguna arquitectura en la que --counter (o counter -, suponiendo que estamos hablando de tipos de datos simples como int, char, etc.) sea más lento que ++counter o counter++ .


Discute esto con más detalle en algún lugar, creo que en su presentación atómica de armas <> . Básicamente, se trata de dónde se necesitan las vallas de memoria en el caso de uso shared_ptr, no de cualquier propiedad intrínseca de los incrementos atómicos frente a los decrementos.

La razón es que realmente no le importa el orden exacto de los incrementos con un puntero inteligente recontado, siempre y cuando no se pierda ninguno, pero con los decrementos, es fundamental que tenga barreras de memoria en su lugar para que en su decremento final lo que desencadena la eliminación, no tiene ninguna posibilidad de accesos de memoria anteriores desde otro hilo al objeto propiedad del puntero inteligente que se reordena una vez que se libera la memoria.


El problema al que se refiere Sutter es el hecho de que el incremento en el recuento de referencias no requiere ninguna acción de seguimiento para su corrección. Está llevando un recuento de referencia distinto de cero a otro recuento distinto de cero, por lo que no es necesario realizar ninguna otra acción. Sin embargo, la disminución requiere una acción de seguimiento para la corrección. El decremento lleva un recuento de referencia distinto de cero a un recuento de referencia distinto de cero o cero, y si su decremento del recuento de referencia llega a cero, debe realizar una acción, específicamente, desasignar el objeto al que se hace referencia. Esta dinámica de decremento y acción requiere una mayor coherencia, tanto en el nivel de la cerca (por lo que la desasignación no se reordena con otra lectura / escritura en otro núcleo que la lógica de la gestión de memoria / caché de la CPU reordenó) y en nivel del compilador (por lo tanto, el compilador no reordena las operaciones en torno al decremento que podría causar que las lecturas / escrituras se reordenen en torno a la posible desasignación).

Por lo tanto, para el escenario descrito por Sutter, la diferencia en costo entre incremento y decremento no está en las operaciones fundamentales en sí mismas, sino en las restricciones de coherencia impuestas sobre el uso real del decremento (específicamente, actuando en el decremento mismo) que no aplicar al incremento.