tipos resueltos programas principiantes para funciones ejercicios ejemplos descargar datos comandos basicos c++ optimization clock

resueltos - funciones en c++



¿Es legal que un optimizador de C++ reordene las llamadas a clock()? (7)

Al menos según mi lectura, no, esto no está permitido. El requisito de la norma es (§1.9 / 14):

Cada cómputo de valor y efecto secundario asociado con una expresión completa se secuencia antes de que se evalúe cada cálculo de valor y efecto secundario asociado con la siguiente expresión completa.

El grado en que el compilador puede reordenar más allá de eso está definido por la regla "como si" (§1.9 / 1):

Esta Norma Internacional no exige ningún requisito sobre la estructura de implementaciones conformes. En particular, no necesitan copiar o emular la estructura de la máquina abstracta. Por el contrario, se requieren implementaciones adecuadas para emular (solo) el comportamiento observable de la máquina abstracta como se explica a continuación.

Eso deja la pregunta de si el comportamiento en cuestión (el resultado escrito por cout ) es un comportamiento oficialmente observable. La respuesta corta es que sí, es (§1.9 / 8):

Los requisitos mínimos en una implementación conforme son:
[...]
- Al finalizar el programa, todos los datos escritos en los archivos serán idénticos a uno de los resultados posibles que la ejecución del programa de acuerdo con la semántica abstracta habría producido.

Al menos mientras lo leo, eso significa que las llamadas al clock podrían reordenarse en comparación con la ejecución de su cómputo largo si y solo si todavía producía un resultado idéntico a la ejecución de las llamadas en orden.

Sin embargo, si desea tomar medidas adicionales para garantizar un comportamiento correcto, puede aprovechar otra disposición (también §1.9 / 8):

- El acceso a objetos volátiles se evalúa estrictamente de acuerdo con las reglas de la máquina abstracta.

Para aprovechar esto, modificarías tu código ligeramente para convertirte en algo así como:

auto volatile t0 = clock(); auto volatile r = veryLongComputation(); auto volatile t1 = clock();

Ahora, en lugar de tener que basar la conclusión en tres secciones separadas del estándar, y aún teniendo solo una respuesta bastante cierta, podemos ver exactamente una oración y tener una respuesta absolutamente cierta: con este código, el reordenamiento utiliza de clock vs., el cómputo largo está claramente prohibido.

La cuarta edición del lenguaje de programación de C ++ , página 225 lee: Un compilador puede reordenar el código para mejorar el rendimiento siempre que el resultado sea idéntico al del orden de ejecución simple . Algunos compiladores, por ejemplo, Visual C ++ en modo de lanzamiento, reordenarán este código:

#include <time.h> ... auto t0 = clock(); auto r = veryLongComputation(); auto t1 = clock(); std::cout << r << " time: " << t1-t0 << endl;

en esta forma:

auto t0 = clock(); auto t1 = clock(); auto r = veryLongComputation(); std::cout << r << " time: " << t1-t0 << endl;

que garantiza un resultado diferente al código original (cero vs. mayor que el tiempo cero informado). Vea mi otra pregunta para un ejemplo detallado. ¿Es este comportamiento compatible con el estándar de C ++?


Bueno, hay algo llamado Subclause 5.1.2.3 of the C Standard [ISO/IEC 9899:2011] que dice:

En la máquina abstracta, todas las expresiones se evalúan según lo especificado por la semántica. Una implementación real no necesita evaluar parte de una expresión si puede deducir que su valor no se usa y que no se producen efectos secundarios necesarios (incluidos los causados ​​por llamar a una función o acceder a un objeto volátil).

Por lo tanto, realmente sospecho que este comportamiento , el que usted describió, es compatible con el estándar .

Además, la reorganización de hecho tiene un impacto en el resultado del cálculo, pero si se mira desde la perspectiva del compilador, vive en el mundo int main() y al hacer mediciones de tiempo, se asoma, le pide al kernel que le dé el actual tiempo, y vuelve al mundo principal donde el tiempo real del mundo exterior realmente no importa. El reloj () en sí no afectará el programa y las variables y el comportamiento del programa no afectarán a la función clock ().

Los valores de los relojes se usan para calcular la diferencia entre ellos; eso es lo que solicitó. Si algo está sucediendo, entre las dos mediciones, no es relevante desde la perspectiva de los compiladores, ya que lo que usted solicitó fue la diferencia del reloj y el código entre la medición no afectará la medición como un proceso.

Sin embargo, esto no cambia el hecho de que el comportamiento descrito es muy desagradable.

Aunque las mediciones imprecisas son desagradables, podrían empeorar mucho más e incluso resultar peligrosas.

Considere el siguiente código tomado de este sitio :

void GetData(char *MFAddr) { char pwd[64]; if (GetPasswordFromUser(pwd, sizeof(pwd))) { if (ConnectToMainframe(MFAddr, pwd)) { // Interaction with mainframe } } memset(pwd, 0, sizeof(pwd)); }

Cuando se compila normalmente, todo está bien, pero si se aplican optimizaciones, la llamada al memset se optimizará, lo que puede ocasionar una falla grave de seguridad. ¿Por qué se optimiza? Es muy simple; el compilador vuelve a pensar en su mundo main() y considera que el memset es una tienda muerta ya que la variable pwd no se usa después y no afectará al programa en sí.


Ciertamente no está permitido, ya que cambia, como habrás notado, el comportamiento observable (salida diferente) del programa (no entraré en el caso hipotético de que veryLongComputation() podría no consumir ningún tiempo mensurable, dada la función de nombre, presumiblemente no es el caso. Pero incluso si ese fuera el caso, en realidad no importaría). No esperaría que se pueda reordenar fopen y fwrite , ¿verdad?

Ambos t0 y t1 se usan para generar t1-t0 . Por lo tanto, las expresiones del inicializador para t0 y t1 deben ejecutar, y al hacerlo, deben seguir todas las reglas estándar. El resultado de la función se utiliza, por lo que no es posible optimizar la llamada a la función, aunque no depende directamente de t1 o viceversa, por lo que uno puede ingenuamente inclinarse a pensar que es legal moverlo, ¿por qué? no. ¿Tal vez después de la inicialización de t1 , que no depende del cálculo?
Indirectamente, sin embargo, el resultado de t1 depende, por supuesto , de los efectos secundarios de veryLongComputation() (notablemente el tiempo de toma de cómputo, si no más), que es exactamente una de las razones por las cuales existe tal cosa como "punto de secuencia".

Hay tres puntos de secuencia de "fin de expresión" (más tres SP de "final de función" y "fin de inicializador"), y en cada punto de secuencia se garantiza que se hayan realizado todos los efectos secundarios de evaluaciones anteriores, y ningún lado los efectos de las evaluaciones posteriores aún no se han realizado.
No hay forma de que pueda mantener esta promesa si se mueve alrededor de las tres afirmaciones, ya que los posibles efectos secundarios de todas las funciones llamadas no se conocen . El compilador solo puede optimizar si puede garantizar que mantendrá la promesa. No puede, dado que las funciones de la biblioteca son opacas, su código no está disponible (ni el código dentro de veryLongComputation , necesariamente conocido en esa unidad de traducción).

Sin embargo, los compiladores a veces tienen "conocimientos especiales" sobre las funciones de la biblioteca, como algunas funciones que no regresan o que pueden regresar dos veces (piense en exit o setjmp ).
Sin embargo, dado que cada función no vacía y no trivial (y veryLongComputation es bastante no trivial desde su nombre) consumirá tiempo, un compilador que tenga "conocimiento especial" sobre la función de librería de clock otro modo opaca, tendría que ser explícitamente prohibido de reordenar llamadas alrededor de este, sabiendo que hacerlo no solo puede afectar los resultados sino que también afectará.

Ahora la pregunta interesante es por qué el compilador hace esto de todos modos? Puedo pensar en dos posibilidades. Tal vez su código desencadena una heurística de "apariencia similar" y el compilador intenta engañar, quién sabe. No sería la primera vez (piense SPEC2000 / 179.art, o SunSpider para dos ejemplos históricos). La otra posibilidad sería que en algún lugar dentro de veryLongComputation() , inadvertidamente invoque un comportamiento indefinido. En ese caso, el comportamiento del compilador incluso sería legal.


El compilador no puede intercambiar las dos llamadas de clock . t1 debe configurarse después de t0 . Ambas llamadas son efectos secundarios observables. El compilador puede reordenar cualquier cosa entre esos efectos observables, e incluso sobre un efecto secundario observable, siempre que las observaciones sean consistentes con las posibles observaciones de una máquina abstracta.

Como la máquina abstracta C ++ no está formalmente restringida a velocidades finitas, podría ejecutar veryLongComputation() en tiempo cero. El tiempo de ejecución en sí no se define como un efecto observable. Las implementaciones reales pueden coincidir con eso.

Eso sí, mucha de esta respuesta depende del estándar C ++ que no impone restricciones a los compiladores.


Sí, es legal, si el compilador puede ver la totalidad del código que ocurre entre las llamadas al clock() .


Si veryLongComputation() realiza internamente cualquier llamada de función opaca, entonces no, porque el compilador no puede garantizar que sus efectos secundarios sean intercambiables con los de clock() .

De lo contrario, sí, es intercambiable.
Este es el precio que paga por usar un idioma en el que el tiempo no es una entidad de primera clase.

Tenga en cuenta que la asignación de memoria (como new ) puede incluirse en esta categoría, ya que la función de asignación puede definirse en una unidad de traducción diferente y no compilarse hasta que la unidad de traducción actual ya esté compilada. Entonces, si solo asigna memoria, el compilador se ve obligado a tratar la asignación y la desasignación como las barreras más desfavorables para todo: clock() , barreras de memoria y todo lo demás, a menos que ya tenga el código para el asignador de memoria y puede probar que esto no es necesario. En la práctica, no creo que ningún compilador realmente vea el código del asignador para tratar de probar esto, por lo que este tipo de llamadas funcionan como barreras en la práctica.


Supongamos que la secuencia está en un bucle, y veryLongComputation () lanza al azar una excepción. Entonces, ¿cuántos t0s y t1s se calcularán? ¿Precalcula las variables aleatorias y las reordena en función del precalculado, algunas veces reordenando y otras no?

¿Es el compilador lo suficientemente inteligente como para saber que solo una lectura de memoria es una lectura de la memoria compartida? La lectura es una medida de cuán lejos se han movido las barras de control en un reactor nuclear. Las llamadas de reloj se usan para controlar la velocidad a la que se mueven.

O tal vez el momento es controlando el rechinado de un espejo del telescopio Hubble. LOL

Mover las llamadas de reloj parece demasiado peligroso para dejar las decisiones de los escritores de compilación. Entonces, si es legal, tal vez el estándar sea defectuoso.

IMO.