¿Por qué los compiladores de C ++ no optimizan esta asignación booleana condicional como una asignación incondicional?

117

Considere la siguiente función:

void func(bool& flag)
{
    if(!flag) flag=true;
}

Me parece que si la bandera tiene un valor booleano válido, esto sería equivalente a configurarlo incondicionalmente true, así:

void func(bool& flag)
{
    flag=true;
}

Sin embargo, ni gcc ni clang lo optimizan de esta manera; ambos generan lo siguiente en el -O3nivel de optimización:

_Z4funcRb:
.LFB0:
    .cfi_startproc
    cmp BYTE PTR [rdi], 0
    jne .L1
    mov BYTE PTR [rdi], 1
.L1:
    rep ret

Mi pregunta es: ¿es solo que el código es un caso demasiado especial para preocuparse por optimizarlo, o hay buenas razones por las que dicha optimización no sería deseada, dado que flagno es una referencia a volatile? Parece que la única razón que podría ser es que de flagalguna manera podría tener un valor no true-o- falsesin un comportamiento indefinido en el momento de leerlo, pero no estoy seguro de si esto es posible.

Ruslan
fuente
8
¿Tiene alguna evidencia de que sea una "optimización"?
David Schwartz
1
@ 200_success No creo que sea bueno poner una línea de código con un marcado que no funciona como título. Si desea un título más específico, está bien, pero elija una oración en inglés e intente evitar el código en ella (por ejemplo, ¿ por qué los compiladores no optimizan las escrituras condicionales a escrituras incondicionales cuando pueden demostrar que son equivalentes? O similar). Además, dado que las comillas inversas no se representan, no las use en el título incluso si usa código.
Bakuriu
2
@Ruslan, aunque no parece hacer esta optimización para la función en sí, cuando puede insertar el código, parece que lo hace para la versión insertada. A menudo, solo resulta en una constante de tiempo de compilación de 1uso. godbolt.org/g/swe0tc
Evan Teran

Respuestas:

102

Esto puede afectar negativamente al rendimiento del programa debido a consideraciones de coherencia de la caché . Escribir en flagcada vez que func()se llama ensuciaría la línea de caché que lo contiene. Esto sucederá independientemente del hecho de que el valor que se escribe coincide exactamente con los bits encontrados en la dirección de destino antes de la escritura.


EDITAR

hvd ha proporcionado otra buena razón que impide tal optimización. Es un argumento más convincente en contra de la optimización propuesta, ya que puede resultar en un comportamiento indefinido, mientras que mi respuesta (original) solo abordó aspectos de rendimiento.

Después de reflexionar un poco más, puedo proponer un ejemplo más de por qué los compiladores deberían estar estrictamente prohibidos, a menos que puedan demostrar que la transformación es segura para un contexto particular, de introducir la escritura incondicional. Considere este código:

const bool foo = true;

int main()
{
    func(const_cast<bool&>(foo));
}

Con una escritura incondicional, func()esto definitivamente desencadena un comportamiento indefinido (escribir en la memoria de solo lectura terminará el programa, incluso si el efecto de la escritura sería, de lo contrario, no operativo).

León
fuente
7
También puede tener un impacto positivo en el rendimiento, ya que se deshace de una rama. Así que no creo que este caso en particular sea significativo para discutir sin un sistema muy específico en mente.
Lundin
3
La definición de comportamiento de @Yakk no se ve afectada por la plataforma de destino. Decir que terminará el programa es incorrecto, pero la propia UB puede tener consecuencias de gran alcance, incluidos los demonios nasales.
John Dvorak
16
@Yakk Eso depende de lo que se quiera decir con "memoria de sólo lectura". No, no está en un chip ROM, pero muy a menudo está en una sección cargada en una página que no tiene acceso de escritura habilitado, y obtendrá, por ejemplo, una señal SIGSEGV o una excepción STATUS_ACCESS_VIOLATION cuando intente escribir en ella.
Random832
5
"esto definitivamente desencadena un comportamiento indefinido". No. El comportamiento indefinido es una propiedad de la máquina abstracta. Es lo que dice el código lo que determina si UB está presente. Los compiladores no pueden causarlo (aunque si tiene errores, un compilador puede hacer que los programas se comporten incorrectamente).
Eric M Schmidt
7
Es la eliminación de constpara pasar a una función que puede modificar los datos que es la fuente del comportamiento indefinido, no la escritura incondicional. Doctor, duele cuando hago esto ....
Spencer
48

Aparte de la respuesta de Leon sobre el rendimiento:

Suponer flag es true. Suponga que dos hilos están llamando constantemente func(flag). La función tal como está escrita, en ese caso, no almacena nada flag, por lo que debería ser seguro para subprocesos. Dos hilos acceden a la misma memoria, pero solo para leerla. Establecer incondicionalmente flaga truesignifica que dos subprocesos diferentes escribirían en la misma memoria. Esto no es seguro, no es seguro incluso si los datos que se escriben son idénticos a los datos que ya están allí.


fuente
9
Creo que esto es el resultado de la aplicación [intro.races]/21.
Griwes
10
Muy interesante. Entonces leí esto como: Al compilador nunca se le permite "optimizar" una operación de escritura donde la máquina abstracta no tendría una.
Martin Ba
3
@MartinBa En su mayoría. Pero si el compilador puede probar que no importa, por ejemplo, porque puede probar que ningún otro subproceso podría tener acceso a esa variable en particular, entonces puede estar bien.
13
Esto solo es inseguro si el sistema al que se dirige el compilador lo hace inseguro . Nunca me he desarrollado en un sistema donde escribir 0x01en un byte que ya 0x01causa un comportamiento "inseguro". En un sistema con acceso a memoria word o dword lo haría; pero el optimizador debe ser consciente de esto. En una PC moderna o un sistema operativo de teléfono, no ocurre ningún problema. Entonces esta no es una razón válida.
Yakk - Adam Nevraumont
4
@Yakk En realidad, pensando aún más, creo que esto es correcto después de todo, incluso para los procesadores comunes. Creo que tiene razón cuando la CPU puede escribir en la memoria directamente, pero supongamos que flagestá en una página de copia sobre escritura. Ahora, a nivel de CPU, el comportamiento podría estar definido (error de página, deje que el sistema operativo lo maneje), pero a nivel de sistema operativo, todavía puede estar indefinido, ¿verdad?
13

No estoy seguro sobre el comportamiento de C ++ aquí, pero en C la memoria podría cambiar porque si la memoria contiene un valor distinto de cero que no sea 1, permanecería sin cambios con la verificación, pero cambiaría a 1 con la verificación.

Pero como no soy muy fluido en C ++, no sé si esta situación es posible.

glglgl
fuente
¿Sería esto cierto todavía _Bool?
Ruslan
5
En C, si la memoria contiene un valor que la ABI no dice que sea válido para su tipo, entonces es una representación de trampa y leer una representación de trampa es un comportamiento indefinido. En C ++, esto solo podría suceder cuando se lee un objeto no inicializado y está leyendo un objeto no inicializado que es UB. Pero si puede encontrar un ABI que diga que cualquier valor distinto de cero es válido para el tipo bool/ _Booly significa true, entonces en ese ABI en particular, probablemente tenga razón.
1
@Ruslan Con compiladores que usan Itanium ABI y en procesadores ARM, C _Booly C ++ boolson del mismo tipo o tipos compatibles que siguen las mismas reglas. Con MSVC, tienen el mismo tamaño y alineación, pero no hay una declaración oficial sobre si usan las mismas reglas.
Justin Time - Reincorpora a Monica
1
@JustinTime: C de <stdbool.h>incluye un typedef _Bool bool; Y sí, en x86 (al menos en la V ABI System), bool/ _Boolestán obligados a ser 0 o 1, con los bits superiores del byte borran. No creo que esta explicación sea plausible.
Peter Cordes
1
@JustinTime: Eso es cierto, debería haber señalado que definitivamente tiene la misma semántica en todos los sabores x86 de System V ABI, que es de lo que trata esta pregunta. (Puedo decirlo porque el primer argumento funcse pasó en RDI, mientras que Windows usaría RDX).
Peter Cordes