viernes, 9 de febrero de 2018

Release vs. Debug

He visto cosas que ustedes no creerían: programadores haciendo benchmarks de código sin optimizar, generando instaladores con ejecutables en modo debug, desdoblando loops a mano, haciendo inlining mediante copy-paste, distribuyendo los .o y olvidándose los dlls, rayos-C++ brillar en la oscuridad cerca de la puerta de Tannhäuser... Y hablo de alumnos avanzados de la carrera, hasta algunos egresados.

Pensaba en escribir un post sobre cómo distribuir un programa generado con ZinjaI (mayormente sobre qué va en el instalador), pero inmediatamente noté que un punto importante era de esto de compilar en modo "release"... y entonces me parece mejor aclarar ese punto antes de continuar, y dejar lo del instalador para la próxima.


"Release" y "Debug" no son más que nombres. El compilador acepta que le configuremos mil opciones. A ciertos tipos de configuraciones les solemos llamar  "release" y a otro tipo "debug".  Hay dos aspectos principales que distinguen un tipo del otro: el nivel de optimizaciones, y la información de depuración.

Diferencias usuales entre perfiles Debug y Release en ZinjaI. Además de las básicas (info de
depuración nivel 2 en debug, optimizaciones nivel 2/3 en release), la macro _DEBUG puede
servir para habilitar código especial con un #ifdef, y NDEBUG deshabilita los asserts.

1. Optimizaciones

Compilar es traducir de un lenguaje a otro de más bajo nivel. A los fines de este post, de C/C++ a código de máquina, para poder ejecutarlo. Y es importante destacar que hay tantas diferencias entre un lenguaje y otro, que una instrucción de C++ puede significar cientos en código de máquina, y que hay usualmente muchísimas formas válidas de traducir un mismo bloque de código.

El compilador solo tiene la responsabilidad de que el programa se vea como si hiciera lo que le pedimos. Por dentro puede cambiar todo, agregar o quitar variables, reordenar instrucciones, reescribir algoritmos, eliminar bloques de código, lo que quiera mientras que de afuera no se note. Y es capaz de hacer muuucha magia con tal de que el ejecutable sea más rápido. Los he visto analizar algoritmos no triviales O(n) y reemplazarlos por cálculos cerrados O(1) (hasta los he visto probar y refutar el Último Teorema de Fermat).

El "modo release" usualmente activa todas las optimizaciones importantes, para que el resultado sea veloz; mientras que el debug hace lo contrario. No es para que el resultado sea lento, sino para que al depurar el programa siga nuestras instrucciones. Si el compilador nos reescribió todo el algoritmo, al depurar veremos que el control va y viene sin sentido, que parece que algunas variables ya no existen, que partes del programa son inaccesibles, etc.

Pero además, porque usualmente mientras programamos el ciclo editar-compilar-ejecutar se repite muchas veces; y entonces necesitamos que esté aceitado, que recompile rápido. Optimizar cuesta trabajo: suele tomar más tiempo y requerir más memoria.

Diferencias usuales entre perfiles Debug y Release en ZinjaI. Se puede eliminar la info de depuración  al enlazar
(aún la de bibliotecas externas). Y en casos extremos también se pueden aplicar optimizaciones que atraviesen
 CPPs (pero que consumen muchísimo tiempo y memoria extra al compilar). Además, para una aplicación gráfica,
 mostrar la consola en debug puede servirle a quienes "depuran" o loggen a base de cout/cerr/clog o similar.

2. Información de depuración

Como el código de máquina, que es el que realmente se ejecuta, no se parece en nada al fuente; el compilador deja dentro del ejecutable mensajes para el depurador, que le permiten luego al segundo saber qué grupo de instrucciones de máquina se corresponden con qué lineas de código, qué direcciones de memoria con qué variables, etc. Todo esto es lo que se conoce como "información de depuración".

Esta información suele ser mucho más grande que el código mismo, y para los fines de la depuración, cuanto más completa y detallada, mejor. Entonces, no es raro ver programas cuyo código de máquina real sea de unos pocos megas, pero cuya información de depuración ocupe decenas o cientos de megas. Entonces otra diferencia fundamental es el tamaño del ejecutable. Necesitamos esa info para depurar en nuestro entorno de desarrollo; pero normalmente no le sirve para nada al usuario final.

Una alternativa en algunos casos es compilar aún en release con info de depuración y configurar el enlazado para que la mueva fuera del exe, a un archivo separado. Entonces se distribuye solo el exe; pero si algo sale muy mal en producción y no se reproduce en debug, se puede llevar gdb y ese archivo de info para tratar de depurar allí mismo.

Diferencias en tamaño entre compilaciones debug y release. "Strip" es el 
comando/verbo en inglés que se usa para "quitar la info de depuración".

Los compiladores suelen tener cientos de opciones individuales para controlar estas cosas, pero también mecanismos para activar "grupos" o "niveles" (en gcc y clang, por ej, con "-Ox" y "-gx", variando x entre 0 y 3). En ZinjaI, todas las plantillas incluyen perfiles debug y release predeterminados que serán suficientes en el 99% de los casos.

En conclusión, debug y release son solo nombres; pero usualmente el primero se asocia a una configuración sin optimizaciones y que genera mucha información de depuración; mientras que el segundo es todo lo contrario. El primero es útil para el desarrollador, el segundo es para el cliente final.

No hay comentarios:

Publicar un comentario