2010-08-30 8 views
22

Estaba buscando la palabra clave volatile y para qué sirve, y la respuesta que obtuve fue más o menos:¿Qué tipo de optimizaciones previene 'volátil' en C++?

Se utiliza para evitar que el compilador de optimización de distancia código.

Hubo algunos ejemplos, como cuando el hardware de votación asignados en memoria: sin volatile el bucle de sondeo se retira como el compilador puede reconocer que el valor de condición no se cambia nunca. Pero como solo había un ejemplo o tal vez dos, me puse a pensar: ¿hay otras situaciones en las que necesitemos usar volatile en términos de evitar una optimización no deseada? ¿Las variables de condición son el único lugar donde se necesita volatile?

Imagino que la optimización es específica del compilador y, por lo tanto, no está especificada en la especificación C++. ¿Eso significa que tenemos que ir por instinto, diciendo Hm, sospecho que mi compilador eliminará esto si no declaro esa variable como volatile o hay alguna regla clara para seguir?

+10

¿Es "no usar' volátil' "una regla aceptable? Porque es bastante bueno. No es que 'volátil 'nunca sea útil. Lo es, es solo que, en general, si no está seguro si lo necesita, probablemente no lo haga. –

+0

@Dennis: +1, y comentado como tal en mi respuesta. –

+0

@DennisZickefoose "_Is" no utiliza 'volátil'" una regla aceptable? _ "Diría: no use' volátil' a menos que algún estándar oficial, texto de referencia o documentación lo indique. – curiousguy

Respuesta

23

Básicamente, volatile anuncia que un valor puede cambiar detrás de la parte posterior de su programa. Eso evita que los compiladores guarden en caché el valor (en un registro de CPU) y optimizan los accesos a ese valor cuando parecen innecesarios desde el punto de vista de su programa.

Lo que debería desencadenar el uso de volatile es cuando un valor cambia a pesar de que su programa no ha escrito en él, y cuando no hay otras barreras de memoria (como mutexes como las usadas para programas de subprocesos múltiples).

+0

Aunque creo que casi todas las respuestas a esta pregunta son más o menos útiles (y me gustaría haberlas agrupado todas en una para aceptarlas, jeje), diría que ésta lo resume. – gablin

+0

Entonces, bajo este modelo, dos escrituras consecutivas a un 'volátil' se pueden colapsar en una, ¿verdad? Porque su descripción solo cubre las optimizaciones que afectan a las lecturas. – BeeOnRope

4

Volátil no intenta mantener los datos en un registro de la CPU (100 veces más rápido que la memoria). Tiene que leerlo de memoria cada vez que se usa.

+0

'volátil' no excluye el valor de la memoria caché L1, que cuesta solo unos pocos ciclos de acceso. Sin embargo, está asociado con otros mecanismos que sí lo hacen. Los registros de dispositivo siempre serán volátiles, y a menudo serán incluso más lentos que DRAM. – Potatoswatter

+0

@Potatoswatter ¿La caché L1 no está controlada por hardware? No sabía que el software podría afectar algo allí. –

+1

@Potatoswatter: Si bien es cierto que no existe una necesidad real de una variable volátil para llegar a la memoria real (podría depender de la arquitectura), el hecho es que puede tener un impacto mucho mayor que algunos ciclos . Si la variable está en la misma línea de caché que cualquier variable en uso por otra CPU, cada operación en la variable 'volátil 'desencadenará la sincronización de la memoria caché a las otras CPU y eso puede ser costoso tanto en el' volátil' como en el no vars volátiles en la misma línea de caché. –

10

Las variables de condición son no donde es necesario volatile; estrictamente solo es necesario en los controladores de dispositivo.

volatile garantiza que las lecturas y escrituras en el objeto no se optimizan, o se reordenan con respecto a otra volatile. Si está ocupado, haciendo un bucle en una variable modificada por otro hilo, debe declararse volatile. Sin embargo, no deberías busy-loop. Debido a que el lenguaje no fue realmente diseñado para multihilo, esto no está muy bien soportado. Por ejemplo, el compilador puede mover una escritura a no -variable volátil de más adelante al anterior, lo que infringe el bloqueo. (Para spinloops indefinidos, esto solo puede ocurrir en C++ 0x.)

Cuando llama a una función de biblioteca de hilos, actúa como una valla de memoria, y el compilador supondrá que todos y cada uno de los valores han cambiado, esencialmente todo es volátil Esto es especificado o tácitamente implementado por cualquier biblioteca de enhebrado para mantener las ruedas girando suavemente.

C++ 0x podría no tener este inconveniente, ya que introduce una semántica formal de subprocesamiento múltiple. No estoy realmente familiarizado con los cambios, pero en aras de la compatibilidad con versiones anteriores, no es necesario declarar nada volátil que no fuera antes.

+1

"No se permite que las variables locales sean volátiles en absoluto, aunque algunos compiladores pueden soportarlo y puede elegir acceder a un local exclusivamente por volátil *" -> He visto variables locales volátiles, en loops de espera para sistemas integrados y pronto. ¿Puedes proporcionar una referencia de estándares para eso? También tenga en cuenta que si tiene un objeto 'int', pero simplemente tiene acceso a él por' int vollatile & '(o por desreferenciar un' int volatile * '), esto no se considera una lectura/escritura volátil, por lo que el compilador puede optimizarlo lejos. –

+1

Tenga en cuenta que el famoso [C++ y los peligros del bloqueo controlado de forma doble] (http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf) utiliza moldes para 'int volátil &' en todo el lugar, y utiliza una cita en particular del estándar C++ 03 que dice "El comportamiento observable de la máquina abstracta es su secuencia de lecturas y escrituras en datos volátiles y llamadas a funciones de E/S de la biblioteca". –

+1

Interpreta que tal como la volatilidad de la * ruta de acceso * es suficiente para hacer que un acceso sea observable, pero C++ 03 también dijo "Los requisitos mínimos en una implementación conforme son: [..] En puntos de secuencia, los objetos volátiles son estables en el sentido de que las evaluaciones previas están completas y las evaluaciones posteriores aún no han ocurrido ". Tenga en cuenta que este texto es claro de que solo el acceso a los * objetos volátiles * y no a la * ruta de acceso * solo determina si el acceso es o no un comportamiento observable. –

19

El comportamiento observable de un programa en C++ se determina mediante lectura y escritura en variables volátiles, y cualquier llamada a funciones de entrada/salida.

Lo que esto implica es que todas las lecturas y escrituras a las variables volátiles debe suceder en el orden en que aparecen en el código, y ellos deben suceder. (Si un compilador rompió una de esas reglas, estaría rompiendo la regla de si ...)

Eso es todo. Se usa cuando necesita indicar que leer o escribir una variable se debe ver como un efecto observable. (Nota, los "C++ and the Perils of Double-Checked Locking" article toques en esta un poco.)


Así que para responder a la pregunta del título, que impide cualquier optimización que podría volver a pedir la evaluación de las variables volátiles en relación con otras variables volátiles.

Eso significa un compilador que cambia:

int x = 2; 
volatile int y = 5; 
x = 5; 
y = 7; 

Para

int x = 5; 
volatile int y = 5; 
y = 7; 

está muy bien, ya que el valor de x no es parte de la conducta observable (que no es volátil). Lo que no estaría bien es cambiar la asignación de 5 a una asignación a 7, porque esa escritura de 5 es un efecto observable.

+1

+1: ¡Buena respuesta! – Chubsdad

+0

"' volátil y' "La regla' int' implícita no existe en C++. – curiousguy

+0

@ curiousguy: De hecho, un viejo error tipográfico. – GManNickG

0

generalmente el compilador asume que un programa tiene un solo subproceso, por lo tanto, tiene un conocimiento completo de lo que está sucediendo con los valores de las variables. un compilador inteligente puede demostrar que el programa se puede transformar en otro programa con semántica equivalente pero con un mejor rendimiento. por ejemplo

x = y+y+y+y+y; 

se puede transformar a

embargo
x = y*5; 

, si una variable se puede cambiar fuera del hilo, el compilador no tiene un conocimiento completo de lo que está pasando simplemente examinando esta pieza de código. ya no puede hacer optimizaciones como arriba. (edición: es probable que pueda, en este caso, necesitamos ejemplos más sofisticados)

por defecto, para la optimización del rendimiento, se supone que el acceso solo hilo. esta suposición es generalmente cierta. a menos que el programador indique explícitamente lo contrario con la palabra clave volatile.

+0

En realidad, creo que el plegado constante sigue siendo válido en los artículos 'volátiles', que es esencialmente lo que has mostrado aquí. –

+0

No soy experto en C++. en java, el volátil debe ser recuperado 5 veces. – irreputable

+2

@Billy: Estoy de acuerdo en que la respuesta es poco clara, pero para aclarar: si 'y' es volátil, entonces cambiar' x = y + y' en 'x = 2 * y' es * no * correcto. Pero cambiar 'y = 2 + 2' a' y = 4' está bien. – GManNickG

1

menos que esté en un sistema embebido, o que está escribiendo controladores de hardware en que se utiliza la asignación de memoria como los medios de comunicación, debe nunca jamás a utilizar volatile

Considere:

int main() 
{ 
    volatile int SomeHardwareMemory; //This is a platform specific INT location. 
    for(int idx=0; idx < 56; ++idx) 
    { 
     printf("%d", SomeHardwareMemory); 
    } 
} 

tiene que producir un código como:

loadIntoRegister3 56 
loadIntoRegister2 "%d" 
loopTop: 
loadIntoRegister1 <<SOMEHARDWAREMEMORY> 
pushRegister2 
pushRegister1 
call printf 
decrementRegister3 
ifRegister3LessThan 56 goto loopTop 

mientras que sin volatile podría ser:

loadIntoRegister3 56 
loadIntoRegister2 "%d" 
loadIntoRegister1 <<SOMEHARDWAREMEMORY> 
loopTop: 
pushRegister2 
pushRegister1 
call printf 
decrementRegister3 
ifRegister3LessThan 56 goto loopTop 

La suposición sobre volatile es que la posición de memoria de la variable puede ser cambiado.Está forzando al compilador a cargar el valor real de la memoria cada vez que se usa la variable; y le dice al compilador que no está permitido reutilizar ese valor en un registro.

4

Recuerde que la "regla como si" significa que el compilador puede, y debe, hacer lo que quiera, siempre que el comportamiento visto desde fuera del programa en su conjunto sea el mismo. En particular, aunque una variable nombra conceptualmente un área en la memoria, no hay ninguna razón por la que realmente debería estar en la memoria.

Se podría estar en un registro:

Su valor podría ser calculado de distancia, por ejemplo, en:

int x = 2; 
int y = x + 7; 
return y + 1; 

no necesita tener una x y y en absoluto, pero sólo podría sustituirse con:

return 10; 

Y otro ejemplo, es que cualquier código que no afecta el estado desde el exterior podría ser eliminado por completo. P.ej. si pone a cero los datos confidenciales, el compilador puede ver esto como un ejercicio desaprovechado ("¿por qué está escribiendo en lo que no se leerá?") y eliminarlo. volátil puede usarse para evitar que eso suceda.

volátil puede entenderse como que significa que "el estado de esta variable debe considerarse parte del estado visible hacia el exterior, y no interferir con". No se permiten las optimizaciones que lo usarían de otra forma que no sea seguir literalmente el código fuente.

(Una nota C#. Una gran cantidad que he visto en los últimos tiempos en volatile sugiere que la gente está leyendo sobre C++ volatile y aplicándolo a C#, y leer sobre él en C# y su aplicación a C++. Realmente sin embargo, volatile comporta tan diferente entre los dos como para no ser útil considerarlos relacionados).

+0

+1, es importante pensar en el modelo de memoria y el estado visible del programa. El compilador incluso podría descartar el calificador 'volátil 'si puede determinar que la variable no afectará el comportamiento visible. Considere una variable automática declarada volátil en una función que no llama a ninguna otra función. El compilador puede determinar que la variable no se puede sondear fuera del hilo y puede decidir aplicar cualquier optimización que desee. –

+1

@David: lee y escribe sobre los volátiles como parte del comportamiento visible de un programa C++, _by definition_. El optimizador funciona bajo la regla "como si" que permite transformaciones si dejan el comportamiento visible sin cambios. Por lo tanto, las optimizaciones pueden no eliminar lecturas y escrituras de objetos volátiles. – MSalters

+1

@MSalters, David tiene razón, si una variable automática es volátil pero no tiene su dirección pasada a un puntero volátil no automático o potencialmente involucrarse en un salto largo o acceder de otra manera fuera del acceso funcional "normal", entonces no hay forma de que se observe desde el exterior, ya que no hay forma de que nada afuera sepa qué observar. En este caso, podría decidirse que no es realmente volátil y se ha ignorado su volatilidad. –

1

Una forma de pensar en una variable volátil es imaginar que es una propiedad virtual; escribe e incluso lee puede hacer cosas que el compilador no puede saber. El código generado real para escribir/leer una variable volátil es simplemente una escritura de memoria o lectura (*), pero el compilador tiene que considerar el código como opaco; no puede hacer ninguna suposición bajo la cual pueda ser superfluo. El problema no es simplemente asegurarse de que el código compilado advierta que algo ha causado que una variable cambie. En algunos sistemas, incluso las lecturas de memoria pueden "hacer" cosas.

(*) En algunos compiladores, las variables volátiles se pueden agregar, restar, incrementar, decrementar, etc. como operaciones distintas. Es probable que sea útil para un compilador para compilar:

 
    volatilevar++; 

como

 
    inc [_volatilevar] 

ya que esta última forma puede ser atómica en muchos microprocesadores (aunque no en modernos ordenadores multi-core).Es importante señalar, sin embargo, que si la declaración fueron:

 
    volatilevar2 = (volatilevar1++); 

el código correcto no sería:

 
    mov ax,[_volatilevar1] ; Reads it once 
    inc [_volatilevar]  ; Reads it again (oops) 
    mov [_volatilevar2],ax 

ni

 
    mov ax,[_volatilevar1] 
    mov [_volatilevar2],ax ; Writes in wrong sequence 
    inc ax 
    mov [_volatilevar1],ax 

sino

 
    mov ax,[_volatilevar1] 
    mov bx,ax 
    inc ax 
    mov [_volatilevar1],ax 
    mov [_volatilevar2],bx 

Escribiendo el bacalao fuente e de manera diferente permitiría la generación de un código más eficiente (y posiblemente más seguro). Si 'volatilevar1' no le importaba ser leído dos veces y 'volatilevar2' no le importaba ser escrito antes de volatilevar1, a continuación, la división de la declaración en

 
    volatilevar2 = volatilevar1; 
    volatilevar1++; 

permitiría más rápido, más seguro y, posiblemente, el código.

+0

Lo sentimos, pero no puedo encontrar una justificación para su solicitud de pedido sobre 'volatilevar2 = (volatilevar1 ++);'. Solo hay un único punto de secuencia, en el ';'. Por lo tanto, no se garantiza el orden en que se realizan las escrituras. – MSalters

+0

@MSalters: puede que tenga razón en ese punto, en cuyo caso la tercera variación sería aceptable. Por otro lado, la versión más rápida, que usa inc [_volatilevar1], ciertamente no es aceptable a pesar del hecho de que hay casos en los que sería menos propenso a problemas que las versiones más largas. – supercat

Cuestiones relacionadas