El Perfilador de Código (Code Profiler) es la característica que puedes utilizar en Xojo para medir la cantidad de tiempo que requiere la ejecución de cada uno de los métodos de tu aplicación. Esta información te permite centrarte en optimizar aquellas partes de la aplicación que puedan considerarse como más lentas. Puedes encontrar esta entrada, publicada originalmente en inglés, en este enlace.
Cuando realices un perfilado de rendimiento de la aplicación, recuera la cita dle famoso científico de computación Donald Knuth: “El problema real es que los programadores han invertido una excesiva cantidad de tiempo preocupándose por la eficiencia en los lugares y momento equivocados. La optimización prematura es la raíz de todos los males (o bien de la mayoría de ellos) en programación.”
Esto simplemente significa que no deberías de preocuparte sobre el rendimiento de la aplicación hasta que no tengas un motivo real para ello. La buena noticia es que cuando tengas la necesidad de medir el rendimiento de tus aplicaciones, la característica Code Profiler de Xojo te permitirá identificar con facilidad aquellas áreas que requieren de una mayor cantidad de tiempo para su ejecución.
Usar el Perfilador de Código
Puedes activar o desactivar el Perfilador utilizando el menú Project ↠ Profile. Aparecerá una marca de verificación junto al menú cuando esté activado. Tras haber activado el Perfilador de Cídigo, ejecutarás y probarás las aplicaciones tal y como harías habitualmente. El Perfilador de Código se encargará de reunir información sobre los métodos que se estén ejecutando y la cantidad de tiempo que emplean en su funcionamiento. Ten en cuenta que tu aplicación funcionará algo más lenta de lo habitual debido al hecho de que el Perfilador de Código está activado y esto genera una carga de procesameiento adicional para realizar las mediciones que derivarán en los datos de perfilado. Cuando salgas de la aplicación, aparecerá la sección Profiles en el Navegador de Proyectos con una entrada correspondiente a los datos recogidos.
Cada vez que ejecutas el proyecto, los resultados del perfilador aparecerán en esta ección del Navegador como un elemento separado. Esto te permite comparar los actuales resultados con los anteriores. Puedes eliminar los datos de perfilado haciendo clic con el botón derecho sobre ellos en el Navegador y seleccionando la opción Delete en el menú resultante. Haz clic en Profile Data en el Navegador para mostrar el listado con los Resultados de Perfilado.
Este es un listado de todos los métodos que se han invocado durante la ejecución de la app. Este muestra la cantidad de veces que se ha llamado cada uno de los métodos así como la cantidad de tiempo invertida en cada método.
- Name: El nombre de la clase y el nombre del método. También se muestra el hilo (Thread) en aquellos casos en los que la app utilice dicha capacidad.
- Called: La cantidad total de veces que se ha llamado al método.
- Total: El tiempo total (en milisegundos) invertido en el método.
- Average: El tiempo medio (en milisegundos) invertido en el método. Este valor se corresponde con Total / Called.
Puedes guardar los datos de perfilado a un archivo de texto usando el menú contextual. Sólo has de hacer clic con el botón derecho en cualquier parte del resumen y seleccionar la opción Save As…*.
También puedes desplegar los métodos para ver los datos correspondientes al resto de métodos que se han invocado desde el mismo.
- Cuando se repliega un método, el tiempo mostrado representa la cantidad de tiempo utilizada por el método y cualquiera de los métodos que han sido invocados como resultado de la ejecución de dicho método.
- Cuando se despliega un método, el tiempo mostrado por el método representa la cantidad de tiemp outilizado sólo por el método propiamente dicho. El resto de métodos invocados desde dicho método se muestran bajo el mismo, junto con los tiempos asociados a cada uno de ellos.
A medida que te encuentres optimizando tu app, puedes comparar los datos recientes de perfilado con los datos anteriores para comprobar si los cambios realizados han contribuido a mejorar el rendimiento.
- Los datos de Perfilado no se almacenan junto con el proyecto. Utiliza la función Save As tal y como se ha descrito anteriormente para conservar dicha información.
- Los datos de perfilado sólo se recopilan en aquellos casos en los que la aplicación finalice con normalidad. Si la aplicación sale de forma inesperada (o bien tras pulsar el botón Stop en el Depurador, entonces no se recopilarán datos de perfilado. En el caso de las apps web sólo has de cerrar todas las ventanas/pestañas del navegador y esperar a que el IDE detecte la finalización de la aplicación, o bien puedes añadir un botón o acción que invoque el método Quit.
- No es posible recopilar datos en los casos de las apps macOS a las que se les haya aplicado sandbox.
- El Perfilador de Código no funciona con el Depurador Remoto. Si necesitas utilizar el Perfilador para recopilar información sobre tu app ejecutándose en otra plataforma, entonces has de instalar Xojo sobre dicha plataforma, copiar el proyecto sobre ella y ejecutarlo con el Perfilador de Código activado.
- El Perfilador de Código no funciona con los proyectos iOS.
Utilizar el Perfilador con las Apps Compiladas
Puedes optar por crear una compilación standalone de tu app con código de perfilado embebido. Puedes enviar entonces esta app a los usuarios de modo que les permita generar datos de perfilado que puedas analizar posteriormente. Para ello sólo has de compilar tu app con la opción Profile Code seleccionada en el menú Project. Cuando compilas con la característica Profile Code activada aparecerá un diálogo para recordarte que se incluirá código de perfilado en la app.
Con este código de perfilado empotrado, tu app comenzará a recopilar datos de perfilado a medida que se utilice. Cuando finalice la ejecución, los datos de perfilado se guardarán en un archivo llamado Profile.txt
en la misma carpeta que la app. A continuación, tu usuario podrá enviarte este archivo para su análisis.
Para ver los datos de perfilado, puedes utilizar el proyecto de código abierto Profile-Reader.
Análisis de Rendimiento
Cuando revises los resultados, cada columna te proporcionará información útil, pero no toda esta información te indicará si tienes o no un problema de rendimiento. A continuación encontrarás una serie de sugerencia que puedes tener en cuenta.
Número de llamadas elevado
Deberías de evaluar aquellos métodos que tengan un elevado número de llamadas. Intenta determinar si es necesario llamar el método con tanta frecuencia. Podrás mejorar el rendimiento simplemente reduciendo la cantidad de veces que se llama a un método.
Por ejemplo, quizá tengas un método Refresh al que llames con frecuencia. Al revisarlo puedes darte cuenta de que el método Refresh está llamando a otros métodos de forma innecesaria y que quizá pueda simplificarse. O quizá adviertas que se llama a Refresh con más frecuencia de la necesaria.
Tiempo total elevado
Merece la pena revisar los métodos que tengan un Tiempo Total elevado. Podría ser que el Tiempo Total sea elevado porque se llama al método con mucha frecuencia. Esto es un indicativo de que requiere una revisión. También puede ser que el Tiempo Total sea elevado dado que el método requiere una gran cantidad de tiempo en completarse, si bien esto también significaría que el Tiempo Medio sería igualmente elevado.
En cualquier caso, merecería la pena revisar estos métodos para comprobar si merece la pena mejorarlos.
Tiempo Medio elevado
Los métodos con un Tiempo Medio elevado pueden reducir el rendimiento con rapidez si son invocados con frecuencia. El Tiempo Medio se corresponde simplemente con la división del Tiempo Total por las Llamadas realizadas, de modo que probablemente también quieras revisar estos métodos. Con frecuencia, una simple mejora sobre un método que se llama con frecuencia puede tener un efecto inmediato sobre toda la app.
Técnicas de Optimización
No deberías pasar tiempo optimizando código salvo que exista una caso claro de que necesite optimizarse. Algunas veces es sencillamente más pragmático mover el código que requiere largos periodos de ejecución a un hilo, de modo que el usuario no advierta la cantidad de tiempo que requiere en completarse. También pueden darse situaciones en las que el tiempo invertido en optimizar el código no resulta en una mejora suficiente como para compensar la cantidad de tiempo invertido en ello.
Pero si llegas a una situación en la que el código esté funcionando más lento de lo necesario, estas son algunas técnicas que podrán ayudarte.
Elimina cálculos innecesarios en bucles
Si tienes un cálculo que tienen lugar como parte de una condición de bucle, entonces el cálculo se realizará cada vez que se itere el bucle. Algunas veces esto es necesario porque el vaor podría cambiar, pero por lo genera este valor no cambia (lo que se denomina invariante, es decir que nunca varía), de modo que en vez de ello puedes almacenar el valor en una variable para que no sea preciso recalcularlo cada vez.
En este ejemplo, la tasa impositiva se calcula cada vez en el bucle, pero la tasa impositiva no varía durante el funcionamiento del bucle, de modo que el cálculo se realiza de forma innecesaria:
While (billAmount * TaxRate(state)) < 100
AddLineItemToBill
Wend
En vez de ello, puedes almacenar la tasa impositiva:
Dim taxRate As Double = TaxRate(state)
While (billAmount * taxRate) < 100
AddLineItemToBill
Wend
Controles ListBox invisibles
Alimentar con datos los controles ListBox es algo común. Si estás rellenando un ListBox con multitud de datos (lo que de por sí no es una gran idea), entonces podrías probar a poner el ListBox como invisible antes de añadir las filas, haciéndolo visible nuevamente cuando se hayan añadido las filas. Esto puede evitar algunos posibles refrescos de pantalla innecesarios que resulten en llamadas a eventos, mejorando así la velocidad general.
Llamar a menos método
Existe un ligero incremento de sobrecarga por cada llamada a un método. En un bucle es posible que encuentres que simplemente con poner el código en el bucle, en vez de haciéndolo sobre el propio método, mejorará el rendimiento. Esto debe aplicarse de forma juiciosa ya que puede derivar en código ilegible.
También puedes considerar el uso de los métodos del framework que pueden hacer múltiples cosas con simplemente una llamada, en vez de utilizar múltiples llamadas de métodos. Por ejemplo, el uso de AddRow para añadir una nueva fila al ListBox y emplear luego el método Cell para rellenar sus columnas es menos eficiente en comparación con llamar AddRow proporcionando todos los datos para las celdas:
' Ineficiente
MyListBox.AddRow("")
MyListBox.Cell(MyListBox.LastIndex, 0) = "Col0"
MyListBox.Cell(MyListBox.LastIndex, 1) = "Col1"
MyListBox.Cell(MyListBox.LastIndex, 2) = "Col2"
MyListBox.Cell(MyListBox.LastIndex, 3) = "Col3"
' Más eficiente
MyListBox.AddRow("Col0", "Col1", "Col2", "Col3")
Uso de Dim fuera de los bucles
Xojo también te permite utilizar variables Dim dentro de los bloques de código, incluyendo los bloques. Esto puede resultar útil para la organización de código y es lo recomendado de forma general. Pero puedes obtener una mejora de velocidad si declaras la variable fuera del bucle y la reutilizas dentro del bucle.
While someConditionIsTrue
Dim t As Text = CallMethod
Wend
Si lo escribes de esta forma, entonces tu bucle ahorrará algo de tiempo:
Dim t As Text
While someConditionIsTrue
t = CallMethod
Wend
Análisis de Bucle
Tal y como han indicado varios de estos consejos, la mejora de los bucles resulta con frecuencia en la optimización de código. Dado que los bucles se repiten varias veces, cualquier cosa que sea lenta dentro de un bucle se amplificará de forma importante. Por tanto, la mejora del código que se ejecuta dentro del bucle puede resultar con frecuencia en mejoras de velocidad signfiicativa.
Usa un algoritmo o técnica diferentes
En algunos casos tu algoritmo es sencillamente lento y su optimización no impacta de forma significativa en el rendimiento Por ejemplo, si estás buscandos datos de forma secuencial, no exste ningún tipo de optimización que te pueda ayudar de forma significativa. Será probablemente más rápido ordenar los datos y realizar a continuación una búsqueda binaria en los resultados. O bien puede tener sentido poner los datos en una base de datos en memoria y utilizar sus capaciades para realizar una búsqueda más rápida.
Puedes ver más información sobre el Perfilador de Código en esta entrada.