Principios de perfilado
Una actividad estrictamente regulada
Cuando se emprende la mejora del rendimiento de una aplicación, es esencial pasar por varias etapas. Entre ellas se encuentran las siguientes:
-
Perfilado: (del término inglés Profiling) se trata, gracias al uso de herramientas especializadas, de encontrar todos los puntos de contención sencillos en el código y resolverlos. En este libro, hablaremos principalmente de este paso con detalle, ya que a menudo se subestima aunque casi siempre resuelve los problemas de lentitud en una solución de software. En este nivel de análisis, todas las mejoras se suman y es extremadamente raro que un cambio realizado al código para mejorar su rendimiento elimine los efectos de la corrección anterior.
-
Tuning: (puesta a punto) esta segunda etapa corresponde al momento cuando el perfilado ya no muestra ningún cuello de botella. Por tanto, se considera que la aplicación es correcta desde el punto de vista del código. Sin embargo, el rendimiento puede no ser suficiente para su propósito, y la configuración debe ajustarse para mejorar el tiempo de ejecución de cada caso. Estos ajustes dependen en gran medida del contexto y, por tanto, tienen muchas interferencias entre sí. Una mejora en la ocupación de la memoria puede tener como resultado que se consuma más CPU, o un cambio que permita que una situación se ejecute más rápido puede ralentizar otra. La puesta a punto del rendimiento es, por tanto, un arte muy difícil, y de ahí la reputación del complejo campo de las mejoras de rendimiento, mientras que la etapa de perfilado muestra un excelente rendimiento en relación con la dificultad de su aplicación. Siempre hay que tener en cuenta que el rendimiento bruto tiene poco interés. Lo más importante es tener una experiencia de usuario que sea aceptable para el trabajo diario con el software desarrollado. Si, en paralelo con la experiencia del usuario, también es posible limitar al máximo el uso de recursos por razones ecológicas y/o financieras, permitirá combinar lo mejor de ambos mundos.
-
Re-arquitectura: llegado esta etapa, cuando los esfuerzos de puesta a punto no han dado resultados suficientes, es conveniente volver a la etapa de arquitectura (tanto de hardware como de software) de la solución....
Estabilidad de la plataforma
1. ¿Por qué esta regla?
Es esencial que especifiquemos bien en qué plataforma estamos trabajando, y que lo hagamos con todo detalle: versión del software al que nos dirigimos, tipo de base de datos, sistema operativo, tipo de máquina, capacidades de hardware, etc.
Las razones son principalmente cuantitativas. Para comparar los análisis a lo largo del tiempo y, en particular, para saber si un programa mejora o ve deteriorado su rendimiento con cada nueva versión, es necesario poder comparar con una referencia estable.
Pero esta estabilidad es necesaria, sobre todo, en el marco de una campaña de optimización del rendimiento. Si hay que actualizar un banco de pruebas cuando se lanza una nueva versión, es perfectamente posible probarla en el antiguo banco de pruebas al mismo tiempo, y así establecer coincidencias de rendimiento entre los tiempos anteriores y los nuevos.
Por otro lado, a partir del momento en que se han puesto en marcha las mediciones en una versión del software, las iteraciones de optimización deben continuar en la misma plataforma.
2. ¿Cómo aplicar esta regla?
La forma ideal para garantizar que esta restricción se tenga en cuenta adecuadamente es disponer de un proceso de fabricación de software donde se pueda crear un entregable y desplegar un entorno de prueba continuo. Hoy en día, los sistemas DevOps están en el centro de la producción de software, eso hace que en la actualidad sea más fácil aplicar esta regla que antes.
Para ponerla en práctica, hay que crear una rama de código fuente específicamente para las optimizaciones de rendimiento. Esto tiene dos ventajas:
-
La primera, bastante obvia, es que el resto del equipo puede seguir codificando desarrollos en el software, mientras permanece en un código fuente estable. Esto encaja perfectamente con los diversos flujos que se ofrecen hoy en día usando Git.
-
La segunda es que, al final de la campaña de optimización, se facilitará la comparación con las ramas de trabajo y de producción. Lo que permite integrar fácilmente los cambios en estas ramas.
Para completar más este último punto, hay que señalar que, en la gran mayoría...
Neutralidad del entorno
1. El principio
La neutralidad del entorno es la segunda regla básica durante la optimización del software. Puede parecer similar a la necesidad de estabilidad de la plataforma, pero no es así. La restricción anterior se refería al código que se va a optimizar, mientras que ésta se refiere al entorno (o más bien a los entornos, como se ha mencionado anteriormente) que servirá de soporte para las pruebas de optimización.
Es importante que estos entornos sean múltiples. Por ejemplo, si nuestra aplicación es compatible con varios motores de bases de datos y utilizamos un generador de consultas SQL donde deseamos aumentar el rendimiento, debemos asegurarnos de probar nuestras mejoras en todos los motores de bases de datos con los que nuestro software es compatible. Es muy posible que un cambio en el código SQL dé como resultado un mejor rendimiento en un motor y, por el contrario, cause una disminución en otro.
Sin embargo, cada entorno tomado por separado debe ser lo más neutro posible, es decir, no debe influir en los resultados obtenidos de forma arbitraria o variable en el tiempo. Expliquemos este punto con más detalle porque es imposible que el entorno no repercuta en el rendimiento. La CPU utilizada, la memoria disponible e incluso el sistema operativo utilizado modifican el rendimiento de nuestra aplicación. Es esencial que este impacto sea estable en el tiempo. Si nuestra aplicación utiliza IIS (Internet Information Server, el servidor web de Microsoft), es lógico que tenga menos recursos disponibles cuando procesa las solicitudes, pero esto es simplemente parte del sistema que se está midiendo, y lo más importante es que estos recursos se utilizan de forma más o menos determinista en nuestro escenario de prueba.
Si llevamos el razonamiento al absurdo, podríamos ejecutar fácilmente escenarios de pruebas de rendimiento en una máquina donde la CPU disponible en los primeros 30 minutos de una hora sería el doble que en los últimos 30 minutos. La única condición sería simplemente realizar siempre nuestras tomas de rendimiento a la misma hora dentro de una hora. Por supuesto, se recomienda no llegar a estos extremos. A continuación, enumeraremos las principales fuentes de inestabilidad en un entorno....
Establecer objetivos antes del análisis
De las cinco reglas del perfilado de las aplicaciones, la regla de establecer objetivos antes del análisis puede parecer la menos necesaria. Al fin y al cabo, podemos iniciar una campaña de mejora del rendimiento sin cifrar los objetivos, y decir simplemente que queremos hacerlo lo mejor posible en un tiempo determinado. Fijamos el tiempo que se va a dedicar a esta campaña en lugar de fijar el porcentaje de rendimiento que se va a obtener en un escenario determinado. Incluso si queremos tomarnos todo el tiempo necesario para mejorar una aplicación, tendremos que lanzar una versión de nuestro programa antes de que nuestro cliente cambie de proveedor. Sea cual sea la forma en que miremos el problema, nos encontramos con limitaciones.
Como en cualquier desarrollo informático, podemos actuar sobre tres parámetros:
-
Cantidad de optimización a realizar: ¿cuáles son nuestros objetivos en términos de tiempo empleado y en qué escenarios específicos de uso de nuestra aplicación? ¿Conseguir menos de tres segundos entre el inicio del proceso y el despliegue de la ventana principal? ¿Ejecutar un proceso concreto en una instancia específica del programa en menos de un segundo? ¿Iniciar un proceso múltiple (un «molino») que trate un gran número de artículos en la mitad o incluso un tercio de tiempo?...
Mejoras cuantificables
1. ¿Por qué esta regla?
Quien dice objetivos cuantificables dice medios para medirlos. Esta regla es sin duda la más sencilla de las cinco, al menos de explicar: durante una campaña para optimizar el rendimiento de una aplicación, es necesario ponerse de acuerdo de antemano sobre qué se medirá y cómo se hará.
El «qué» corresponde a los escenarios: ¿cuáles son las interacciones más largas con los usuarios? ¿Cuáles son los procesos masivos que nuestros clientes buscan desesperadamente? A menudo es el departamento de pruebas/calidad del software el que identifica estos escenarios por nosotros. A veces será nuestro cliente directamente, y en estos casos es obvio que se necesita una mejora los más rápido posible.
2. ¿Cómo aplicar esta regla?
El «cómo» es a nuestra discreción: podemos optar por añadir contadores de software para el tiempo transcurrido entre el clic sobre un botón y la aparición del resultado en la pantalla, o solicitar a un probador que use un cronómetro y realice algunas pruebas en nuestra aplicación.
No hay que subestimar esta segunda opción: es obviamente menos precisa, pero cuando un cliente nos pide que hagamos una funcionalidad más rápida, generalmente no quiere ahorrar solo unas décimas de segundo...
Granularidad descendente
1. Empecemos con una parábola
Es posible que el lector ya haya oído la parábola de que la vida se compara con llenar un frasco con piedras, guijarros y arena: si empezamos por llenar el frasco con arena y guijarros (lo menos importante de la vida), no podremos meter después las grandes piedras (lo esencial). Pero si llenamos el frasco con rocas, podemos conseguir que algunos guijarros fluyan entre las rocas. Y de la misma manera, la arena puede entonces rellenar los huecos.
Lo mismo ocurre con el perfilado: si el desarrollador empieza por mejorar las funciones menos utilizadas, se dará cuenta de que, al mejorar los principales puntos de contención del software, está deshaciendo el trabajo realizado anteriormente. Por el contrario, si optimiza su código empezando por los mayores puntos de contención (en definitiva, los cuellos de botella), irá sacando poco a poco problemas más pequeños. Especialmente cuando arregla estos pequeños problemas, generalmente no repercute en las correcciones más importantes que ha hecho anteriormente.
2. Un ejemplo
Pongamos un ejemplo: la ventana principal de una aplicación tarda demasiado en aparecer. Un análisis de un perfilador muestra que la función de carga de una biblioteca es demasiado lenta (digamos dos segundos) y decidimos optimizarla. Después de modificar el código, hemos ganado un segundo. Esto no es suficiente y decidimos llevar la optimización más allá, cargando las bibliotecas mientras el usuario se conecta. Sin embargo, las mediciones muestran que el usuario tarda una media de tres segundos en teclear su contraseña. Ahora tenemos ese tiempo (tres segundos) por delante para cargar nuestra biblioteca, y la reducción del tiempo de carga que habíamos hecho anteriormente ya no es útil.
Esto no quiere decir que haya que descartar el trabajo: quizás un día carguemos más bibliotecas durante la fase de autentificación y esta mejora en la velocidad nos sirva entonces. Pero si hemos pasado tres días mejorando la carga, y el cambio de opinión ha puesto en peligro una entrega a un cliente importante, entonces...