Bienvenido, me llamo Miguel y para hoy les traigo otro tutorial.
Hemos escuchado de aquellos de ustedes que usan C ++
que depurar la memoria nativa puede ser bastante difícil, particularmente en los juegos.
Con Android Studio 4.1
, hemos implementado la capacidad de registrar pilas de llamadas de asignaciones de memoria nativa en nuestro Memory Profiler.
La grabación en memoria nativa se basa en el backend de Perfetto , la solución de seguimiento e instrumentación de rendimiento de próxima generación para Android.
Una técnica común cuando se intenta depurar problemas de memoria es comprender qué es la asignación de memoria y qué es la liberación de memoria.
El resto de este artículo lo guiará a través de cómo usar Native Memory Profiler para ayudar a rastrear una fuga, usando la prueba de estrés de emulación de Gpu como proyecto de ejemplo.
Índice
Empezando
Cuando se sospecha una fuga de memoria, a menudo es una buena idea comenzar en un nivel alto y observar patrones en la memoria del sistema.
Para hacer esto, haga clic en el botón de perfil en Android Studio e ingrese al generador de perfiles de memoria para obtener información de seguimiento de memoria más detallada.
Después de ejecutar la simulación varias veces, podemos ver algunos patrones interesantes.
- La memoria de la GPU aumenta como se puede esperar de una aplicación de emulación de GPU; sin embargo, también parece que esta memoria se limpia correctamente una vez finalizada la actividad.
- La memoria nativa crece cada vez que ingresamos a GpuEmulationStressTestActivity, sin embargo, esta memoria no parece reiniciarse después de cada ejecución, lo que podría ser indicativo de una fuga.
Vista de tabla de memoria nativa
A partir de Android Studio 4.1 Canary 6
, podemos obtener una grabación de las asignaciones de memoria nativa para analizar por qué no se libera la memoria.
Para hacer esto con la aplicación de emulación de GPU, detuve la aplicación en ejecución y comencé a perfilar una instancia nueva. Comenzar desde un estado limpio, especialmente cuando se mira un código base desconocido, puede ayudar a reducir nuestro enfoque.
Desde el generador de perfiles de memoria, capturé una grabación de asignación nativa durante toda la demostración de emulación de GPU.
Para hacer esto, reinicie la aplicación seleccionando Ejecutar-> Perfil 'aplicación'
. Después de que se inicie la aplicación y se abra la ventana del perfil, haga clic en el generador de perfiles de memoria y seleccione «registrar asignación nativa»
La vista de tabla es útil para juegos / aplicaciones que usan bibliotecas que implementan sus propios asignadores destacando las llamadas malloc que se realizan fuera de new.
Cuando se carga una grabación, los datos se presentan primero en una tabla. La tabla muestra las funciones de hoja que llaman a malloc. Además del nombre de la función, la tabla muestra módulo, recuento, tamaño y delta.
Esta información se muestrea, por lo que es posible que no se capturen todas las llamadas malloc / gratuitas. Esto depende en gran medida de la frecuencia de muestreo , que se discutirá un poco más adelante.
También es útil saber desde dónde se llaman estas funciones que asignan memoria. Hay dos formas de visualizar esta información.
La primera es cambiando el menú desplegable «Organizar por método de asignación» a «Organizar por pila de llamadas». La tabla muestra un árbol de pilas de llamadas, similar a lo que puede esperar de una grabación de CPU.
Si el proyecto actual tiene símbolos (que suele ser el caso de las compilaciones depurables; si está perfilando un APK externo, consulte la guía aquí ), se recogerán y usarán automáticamente. Esto le permite hacer clic derecho en una función y «Ir a la fuente».
Visualización de memoria (nativa y no nativa)
También hemos agregado una nueva visualización de gráfico de llamas a los perfiladores de memoria, lo que le permite ver rápidamente qué pilas de llamadas son responsables de asignar la mayor cantidad de memoria. Esto es especialmente útil cuando una pila de llamadas es muy profunda.
Hay cuatro formas de ordenar estos datos a lo largo del eje X:
- «Tamaño de asignación» es el valor predeterminado, que muestra la cantidad total de memoria rastreada.
- «Recuento de asignación» muestra el número total de objetos asignados.
- «Tamaño total restante» es el tamaño de la memoria muestreada durante la captura que no se liberó antes del final de la captura.
- El «Recuento total restante», al igual que el tamaño restante, es el recuento de objetos capturados pero no liberados antes del final de la captura.
Desde aquí podemos hacer clic con el botón derecho en las pilas de llamadas y seleccionar «Saltar a la fuente» para llevarnos a la línea de código responsable de la asignación.
Sin embargo, al echar un segundo vistazo a la visualización, notamos que el padre común, WorldState, es responsable de múltiples fugas. Para validar esto, puede ayudar filtrar los resultados.
Al igual que con la vista de tabla, el gráfico se puede filtrar utilizando la barra de filtro. Cuando se usa el filtro, los datos en el gráfico se actualizan automáticamente para mostrar solo las pilas de llamadas que tienen funciones que coinciden con la palabra / expresión regular buscada.
A veces, las pilas de llamadas pueden ser bastante largas o simplemente no hay suficiente espacio para mostrar el nombre de la función en la pantalla.
Para ayudar con esto, ctrl + rueda del mouse acercará / alejará
, o puede hacer clic en el gráfico para usar W, A, S, D para navegar.
Verificando los hallazgos
Agregar un punto de interrupción y ejecutar la Emulación dos veces rápidamente revela que en la segunda ejecución causamos la fuga al sobrescribir el puntero de nuestra primera ejecución.
Como solución rápida a la muestra, podemos eliminar el mundo después de que esté marcado como hecho, perfilando la aplicación nuevamente para validar la solución.
Terminando donde comenzamos mirando las estadísticas de memoria de alto nivel. Validar que eliminar sWorld al final de la simulación libera los 70 MB de nuestra primera ejecución.
Creación de perfiles y configuración de frecuencia de muestreo.
El ejemplo anterior muestra cómo se puede utilizar el seguimiento de la memoria nativa para encontrar y corregir pérdidas de memoria.
Otro uso común del seguimiento de la memoria nativa es comprender a dónde va la memoria durante el inicio de la aplicación.
En Android Studio 4.1
, también agregamos la capacidad de capturar grabaciones de memoria nativa desde el inicio de la aplicación.
Está disponible en el cuadro de diálogo «Configuraciones de ejecución / depuración» en la pestaña «Creación de perfiles».
Puede personalizar el intervalo de muestreo o grabar la memoria al inicio en el cuadro de diálogo Ejecutar configuración.
Aquí también puede cambiar la frecuencia de muestreo para nuevas capturas. Una frecuencia de muestreo menor puede tener un gran impacto en el rendimiento general, mientras que una frecuencia de muestreo mayor puede perder algunas asignaciones.
Diferentes frecuencias de muestreo funcionan para diferentes tipos de problemas de memoria.
Con el nuevo generador de perfiles de memoria nativa, la búsqueda de pérdidas de memoria y la comprensión de dónde se retiene la memoria es un poco más fácil.
Prueba el generador de perfiles de memoria nativa en Android Studio 4.1
y deja cualquier comentario sobre nuestro rastreador de errores.
Para obtener consejos y trucos adicionales, asegúrese de consultar también nuestra charla a principios de este año en la cumbre de Google for Games, las herramientas de memoria de Android y las mejores prácticas.
Espero que te haya sido de utilidad. Gracias por leer este tutorial.
Añadir comentario