¿Por qué el código intentaría activamente evitar la optimización de llamadas finales?

81

El título de la pregunta puede ser un poco extraño, pero la cuestión es que, hasta donde yo sé, no hay nada que hable en absoluto en contra de la optimización de llamadas finales. Sin embargo, mientras navegaba por proyectos de código abierto, ya me encontré con algunas funciones que intentan activamente evitar que el compilador realice una optimización de llamadas finales , por ejemplo, la implementación de CFRunLoopRef, que está llena de tales trucos . Por ejemplo:

static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    if (func) {
        func(observer, activity, info);
    }
    getpid(); // thwart tail-call optimization
}

Me encantaría saber por qué esto es aparentemente tan importante, y ¿hay algún caso en el que yo, como desarrollador normal, deba tener esto en cuenta también? P.ej. ¿Existen errores comunes con la optimización de llamadas de cola?

JustSid
fuente
10
Un posible error podría ser que una aplicación funcione sin problemas en varias plataformas y luego deje de funcionar repentinamente cuando se compila con un compilador que no admite la optimización de llamadas finales. Recuerde que esta optimización no solo puede aumentar el rendimiento, sino también evitar errores de tiempo de ejecución (desbordamientos de pila).
Niklas B.
5
@NiklasB. ¿Pero no es esta una razón para no intentar desactivarlo?
JustSid
4
Una llamada al sistema puede ser una forma segura de calcular el TCO, pero también bastante cara.
Fred Foo
39
Este es un gran momento de aprendizaje para los comentarios adecuados. +1 por explicar parcialmente por qué esa línea está allí (para evitar la optimización de la llamada final), -100 por no explicar por qué la optimización de la llamada
final
16
Dado que el valor de getpid()no se está utilizando, ¿no podría ser eliminado por un optimizador informado (ya que getpides una función que se sabe que no tiene efectos secundarios), permitiendo por lo tanto al compilador hacer una optimización de llamada final de todos modos? Este parece un mecanismo realmente frágil.
luiscubal

Respuestas:

83

Mi suposición aquí es que es para asegurar que __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__esté en el seguimiento de la pila para fines de depuración. Tiene lo __attribute__((no inline))que respalda esta idea.

Si se da cuenta, esa función simplemente va y rebota a otra función de todos modos, por lo que es una forma de trampolín que solo puedo pensar que está ahí con un nombre tan detallado para ayudar a la depuración. Esto sería especialmente útil dado que la función está llamando a un puntero de función que se ha registrado desde otro lugar y, por lo tanto, es posible que esa función no tenga símbolos de depuración accesibles.

Observe también las otras funciones con nombres similares que hacen cosas similares; realmente parece que está ahí para ayudar a ver lo que ha sucedido a partir de un rastreo. Tenga en cuenta que este es el código principal de Mac OS X y también aparecerá en los informes de fallos y en los informes de muestra de procesos.

Mattjgalloway
fuente
Sí, eso es consistente con __attribute__((noinline)). Creo que acertaste aquí.
Niklas B.
Sí, tiene sentido. Pero si observa desde dónde se llaman estas funciones, verá que siempre se llaman solo desde una función, por ejemplo, mi función de ejemplo solo se llama desde la __CFRunLoopDoObserversque definitivamente aparece en el seguimiento de la pila ...
JustSid
1
Claro, pero supongo que es otro marcador de exactamente dónde se está ejecutando la devolución de llamada / bloque / etc. del observador.
mattjgalloway
2
Creo que esta es la mejor respuesta. +1
R .. GitHub DEJA DE AYUDAR A ICE
@R .. Sin embargo, solo puedo aceptar una respuesta y Andrew White también mencionó otros casos en los que la optimización de la llamada final podría no ser deseada. Recuerde, no pregunté por qué la función lo hizo, sino por qué podría no ser deseada en general y le di la función como ejemplo del mundo real.
JustSid
34

Esto es solo una suposición, pero tal vez para evitar un bucle infinito frente a un bombardeo con un error de desbordamiento de pila.

Dado que el método en cuestión no pone nada en la pila, parecería posible que la optimización de la recursividad de la llamada de cola produzca código que ingrese en un bucle infinito en lugar del código no optimizado que pondría la dirección de retorno en la pila que eventualmente se desbordaría en caso de mal uso.

El único otro pensamiento que tengo está relacionado con la conservación de las llamadas en la pila para la depuración y la impresión de seguimiento de la pila.

Andrew White
fuente
8
Creo que la explicación de stacktrace / depuración es mucho más probable (y estaba a punto de publicarla). Un bucle infinito no es realmente peor que fallar, ya que el usuario puede forzar la salida de la aplicación. Eso también explicaría el noinline.
ughoavgfhw
3
@ughoavgfhw: tal vez, pero cuando te metes en el enhebrado y cosas así, los bucles infinitos son realmente difíciles de rastrear. Siempre he tenido la mentalidad de que el mal uso debería desencadenar una excepción. Como nunca tuve que hacer esto, es solo una suposición.
Andrew White
1
sincronicidad, algo así ... Acabo de encontrarme con un error que mantenía una aplicación abriendo nuevas ventanas. Esto me hace pensar, si la aplicación se hubiera bloqueado antes de intentar saturar "el montón" (mi memoria) y asfixiar a X, no habría necesitado cambiar a la terminal para matar abruptamente la aplicación loca (ya que X comenzó pronto a convertirse en insensible). Entonces, ¿tal vez sería una razón para preferir el enfoque de "falla rápida" que podría venir con un desbordamiento de pila y sin optimización ...? ¡O tal vez solo sea un asunto diferente, aunque ...!
ShinTakezou
2
@AndrewWhite Hmm Me encantan los bucles infinitos. No puedo pensar en una sola cosa que sea más fácil de depurar, quiero decir, puedes conectar tu depurador y obtener la posición exacta y el estado del problema sin adivinar. Pero si desea obtener trazas de pila de los usuarios, estoy de acuerdo en que un bucle infinito es problemático, por lo que parece lógico: aparecerá un error en su registro, un bucle infinito no.
Voo
1
Esto supone que la función es recursiva en primer lugar, pero no lo es; ni directamente ni (mirando el contexto de donde proviene la función) indirectamente. Inicialmente hice la misma suposición errónea.
Konrad Rudolph
21

Una posible razón es facilitar la depuración y la creación de perfiles (con el TCO, el marco de la pila principal desaparece, lo que dificulta la comprensión de los seguimientos de la pila).

NPE
fuente
2
Sin embargo, facilitar la creación de perfiles a costa de ralentizar el programa es un poco extraño. Tiene tanto sentido como diluir el aceite antes de medir qué tan lejos puede llegar su automóvil: x
Matthieu M.
1
@MatthieuM .: Tal cosa no tendría sentido si la llamada agregada se realizara millones de veces en un bucle, pero si se ejecuta unos cientos de veces por segundo o menos, puede ser mejor dejarlo en el sistema real y Ser capaz de examinar cómo se comporta el sistema real, en lugar de eliminarlo y arriesgarse a que dicha eliminación haga un cambio sutil pero importante en el comportamiento del sistema.
supercat
@MatthieuM. Si diluir el aceite es un requisito previo para cualquier medición, entonces tiene mucho sentido.
Dmitry Grigoryev
@DmitryGrigoryev: No, no es así. Ninguna medida es molesta, pero una medida incorrecta puede pasar de inútil a peligrosa (dependiendo de la confianza que deposites en ella). Continuando con la analogía del aceite: si lo ralentiza, es posible que obtenga medidas que indiquen que el peso es más importante que la aerodinámica y, por lo tanto, elimine el peso y empeore la aerodinámica para optimizar lo que ha medido ... sin embargo, con aceite real, cuando vas más rápido, resulta que la aerodinámica era más importante y tu "mejora" es peor que no hacer nada.
Matthieu M.
@MatthieuM. ¿Está familiarizado con el principio de incertidumbre? Cualquier medición es incorrecta hasta cierto punto, porque no hay forma de medir nada sin interactuar con el objeto que se está midiendo. Por lo tanto, incluso si no modifica el aceite en su ejemplo, instrumentar el automóvil cambiará la aerodinámica de todos modos.
Dmitry Grigoryev