Hoy, 14 de febrero de 2013, Delphi alcanza la mayoría de edad. A todos
los que trabajan con esta herramienta:
¡Felices 18 años de Delphi!
FastMM 4
Por si no lo sabes, FastMM es un manejador de memoria de código abierto, desarrollado por Pierre le Riche como reemplazo al manejador de memoria de Borland utilizado en Delphi desde el inicio, y que se convirtió en el manejador de memoria por defecto a partir de Delphi 2006.
Eso significa que, si utilizas Delphi 2006 o superior, ya estas usando FastMM, pero probablemente no estás sacando todo el provecho que podrías de él
En este artículo no hablaremos a profundidad sobre las ventajas de FastMM, ni lo compararemos con otros manejadores de memoria existentes. Por hoy, nos centraremos en la opción FullDebugMode de FastMM y veremos como este modo nos ayuda a resolver las fugas de memoria que puede haber en nuestras aplicaciones
La definición de fuga de memoria también escapa al alcance de este artículo pero es un tema del que hay mucha información en el Internet, lo definiremos brevemente como:
- Fuga de memoria
- (memory leak en inglés), es la condición por la cual un programa no libera bloques de memoria que ha reservado pero en realidad ya no está utilizando, y por ello si se ejecuta durante el tiempo suficiente, puede llegar a agotar la memoria disponible para la aplicación o incluso en todo el sistema.
No olvidemos que en Windows y otros sistemas operativos modernos, cuando un proceso termina, el mismo sistema operativo se encarga de liberar toda la memoria asignada a ese proceso, por lo que el problema realmente no ocurre para todo un sistema luego de una o varias ejecuciones del programa que tiene pérdidas de memoria, sino que afecta principalmente al mismo programa durante su ejecución, y puede llegar a afectar todo el sistema si el programa se ejecuta suficiente tiempo para agotar la memoria principal/virtual disponible (dependiendo de la arquitectura y otras variables)
FastMM en FullDebugMode
El FullDebugMode de FastMM es un modo particular en que el manejador de memoria toma algunas medidas especiales para detectar fallos en nuestro programa, entre ellos la memoria no liberada durante la ejecución
Al activarlo, cuando el programa termina, obtenemos un reporte detallado de todos aquellos bloques de memoria que el no se han liberado, incluyendo una traza de la pila al momento que esa memoria fue reservada, lo que nos ayuda a identificar rápidamente la parte de nuestro programa que puede estar produciendo el problema.
Aclaración: Este tipo de detección nos ayuda a detectar solamente los casos en que el programa no reclame la memoria al finalizar. Puede darse el caso de, lo que yo llamaría pérdidas de memoria funcionales, que son aquellas en las que un programa no devuelve memoria que en realidad ya no está utilizando durante su ejecución, y por ello puede llegar a agotar innecesariamente la memoria disponible, pero este programa
si libera toda esta memoria al finalizar. Lamentablemente en esos casos, es poco lo que FastMM puede hacer por nosotros.
¿Cómo activar FullDebugMode?
FastMM se configura mediante directivas condicionales del compilador. Lo que yo habitualmente hago es crear una nueva configuración de construcción para establecer, no solo las directivas de FastMM, sino también otras configuraciones del compilador que nos resultarán útlies. Los pasos a seguir son:
- Descargar la versión completa de FastMM
- Editar el código fuente del proyecto y añadir la unidad FastMM4 a la clausula uses, teniendo el cuidado que sea la primera unidad listada en la misma
- En el ProjectManager, expandir el nodo Build Configurations
- Hacer clic derecho en el nodo debug, y seleccionar la opción New Configuration
- En el diálogo New Build Configuration que aparece, introducir un nombre para la nueva configuración, por ejemplo FullDebugMode y hacer clic en OK
- Verificar que una nueva entrada con el nombre introducido se ha creado en el árbol de configuraciones de construcción de nuestro proyecto y hacer doble clic sobre este para activar esta configuración
La configuración ha sido creada como hija de la entrada Debug para heredar de esta las configuraciones por defecto para este modo. Ahora, solo nos queda hacer unos pequeños ajustes para obtener todo el provecho de FastMM, estos son:
- Entrar a las opciones del proyecto y seleccionar el nodo Compiling (project/options/Delphi Compiler/Compiling), verificar que esté seleccionada la configuración recién creada en Build Configuration en la parte superior del diálogo
- En la sección principal del dialogo, ubicar la entrada Use debug .dcus en la categoría Debugging y establecer su valor a true
- En el árbol de la izquierda ahora seleccionar la categoría Linking, siempre dentro de Delphi Compiler
- En la sección principal ahora ubicar y cambiar los siguientes valores:
- Debug Information debe establecerse a true
- Map file debe establecerse a Detailed
- Place debug information in separate file debe establecerse a true
- En el árbol de la izquierda ahora seleccionar el nodo Delphi Compiler, la primera entrada de la sección principal es Conditional defines, a la cuál añadiremos la definición de los símbolos FullDebugMode; EnableMemoryLeakReporting; UseOutputDebugString; (sin quitar el ya definido DEBUG, y otros que utilices en tu proyecto).
Ahora, compila tu proyecto, busca la carpeta donde se ha creado el EXE (en mi caso es bin\FullDebugMode), y copia a esta carpeta el archivo FastMM_FullDebugMode.dll que viene con la descarga de FastMM. Ahora, ejecuta el programa desde el IDE como normalmente lo haces y utiliza algunas de sus funciones. Al terminar, si hay una fuga de memoria verás un mensaje indicando tal situación, similar a este:
Pero, más importante que eso, tendrás un reporte completo de las fugas en la misma carpeta donde se produjo tu ejecutable, con el nombre %proyecto%_MemoryManager_EventLog.txt, con una entrada como la que sigue por cada dirección de memoria no liberada:
--------------------------------2013/2/14 9:33:55-------------------------------- A memory block has been leaked. The size is: 36 This block was allocated by thread 0x22A0, and the stack trace (return addresses) at the time was: 404A26 [System.pas][System][@GetMem][3693] 409CA6 [System.pas][System][@New][25090] 9F9048 [uDMConsecutivos.pas][uDMConsecutivos][TDMConsecutivos.GetIDConsecutivo][167] 9F92F2 [uDMConsecutivos.pas][uDMConsecutivos][TDMConsecutivos.RegistraConsecutivo][229] 77A4AC59 [Unknown function at RtlUlonglongByteSwap] 9F9228 [uDMConsecutivos.pas][uDMConsecutivos][TDMConsecutivos.RegistraConsecutivo][199] A0117B [UfrmQuickTest.pas][UfrmQuickTest][TfrmQuickTest.Button1Click][28] 76ED14BC [DrawTextExW] 406313 [System.pas][System][@IsClass][11370] 528C37 [Forms.pas][Forms][GetRealParentForm][2171] 4A2E25 [Controls.pas][Controls][TControl.Click][7190] The block is currently used for an object of class: Unknown The allocation number is: 223041
Como se puede ver, este caso me lleva directamente al método de la clase TDMConsecutivos.GetIDConsecutivo, que reserva memoria para un registro con una llamada a New(), pero esa memoria no es liberada posteriormente.
Si tu proyecto no muestra el mensaje al terminar, felicitaciones, tienes un programa que reclama toda la memoria que utiliza al terminar!
Muy interesante 🙂
Estupendo artículo, Antonio. 🙂
Poder elegir un manejador de memoria distinto del predeterminado es la razón de que las funciones GetMem y FreeMem sean «virtuales».
Necesitaria saber como se puede usar esto mismo en Delphi2006.Net ya que el FastMM no me compila por cosas que no andan en esta versión.