Análisis de rendimiento de software

De Wikipedia, la enciclopedia libre

En ingeniería de software el análisis de rendimiento, comúnmente llamado profiling o perfilaje, es la investigación del comportamiento de un programa de ordenador usando información reunida desde el análisis dinámico del mismo. El objetivo es averiguar el tiempo dedicado a la ejecución de diferentes partes del programa para detectar los puntos problemáticos y las áreas dónde sea posible llevar a cabo una optimización del rendimiento (ya sea en velocidad o en consumo de recursos).[1]​ Un profiler puede proporcionar distintas salidas, como una traza de ejecución o un resumen estadístico de los eventos observados.[2]

Usualmente el Profiling es utilizado durante el desarrollo de software como método para la depuración y optimización de los algoritmos, esta práctica vista de esta manera es buena, pero es vista más como una actividad interna que suele carecer de objetividad y veracidad cuando no es evaluado por personal realmente especializado y en el entorno adecuado para ello.[3]

El profiling se puede llevar a cabo en el código fuente o sobre un binario ejecutable mediante una herramienta llamada profiler.

Los profilers pueden clasificarse según la forma de recopilación de datos que utilicen, pudiendo destacar: basados en eventos, estadísticos, con instrumentación de código y como simulación.[2]

Historia[editar]

Las herramientas de análisis de rendimiento ya existían en las plataformas IBM S/360 y IBM System/370 de principios de 1970. Generalmente se basaba en un temporizador de interrupciones que se registraban en el programa de palabra de estado (Program status word, PSW) (que era un contador de programa y un registro de estado a la vez en estas arquitecturas) en intervalos establecidos para detectar hot spots en la ejecución del código.[4]​ Este fue un ejemplo temprano de muestreo en estadística que es un tipo que se verá a continuación. A principios de 1974, el simulador de conjunto de instrucciones permitió realizar trazas completas y otras funciones de supervisión del rendimiento.

Los programas analizadores de rendimiento en Unix se remontan al menos a 1979, cuando los sistemas Unix incluía la herramienta básica prof que aparece cada función y la cantidad de tiempo de ejecución del programa utilizado. En 1982, gprof extendió el concepto a un análisis llamado grafo de llamadas.[5]

En 1994, Amitabh Srivastava y Alan Eustace de Digital Equipment Corporation publican un artículo describiendo ATOM.[6]​ ATOM es una plataforma para convertir un programa en su propio profiler. Es decir, en tiempo de compilación, inserta el código en el programa a ser analizado. Ese código introducido produce salidas de datos de análisis. Esta técnica (modificar un programa para analizarse a sí mismo se conoce como instrumentación).

En 2004, tanto el "paper" de gprof como el de ATOM aparecieron en la lista de los 50 "papers" más influyentes de Programming Language Design and Implementation de todos los tiempos.[7]

Recopilación de los eventos del programa[editar]

Los profilers utilizan una amplia variedad de técnicas para recopilar datos, incluyendo: interrupciones por hardware, instrumentación de código, simulador de conjunto de instrucciones, hooking y contadores de rendimiento de hardware. El profiling es la técnica más usada dentro de la ingeniería de rendimiento.

Perfilaje de tiempo de ejecución basado en funciones y líneas de código[editar]

Es común ver en este tipo de perfilado dos tecnologías: Instrumentación de código y muestreo; La instrumentación se basa en dejar que el compilador modifique cada llamada a una función, insertando algunas líneas de código que registra la llamada, el autor de la llamada y el tiempo que se requirió. Esta técnica viene de la mano con una sobrecarga significativa de trabajo, esto puede afectar de forma negativa en códigos donde se tenga una cantidad alta de funciones con tiempos de ejecución cortos. Sin embargo, esta técnica se encuentra limitada a solamente funciones o bloques simples, esto debido a razones de eficiencia. El muestreo por otro lado tiende a ser una opción menos invasiva, ya que funciona interrumpiendo el programa de forma periódica en intervalos pequeños, durante las interrupciones se registra el contador de eventos. Aunque este proceso es estadístico por naturaleza es importante denotar que a medida que la ejecución de nuestro código se prolongue, los resultados obtenidos serán mucho más precisos.

Perfilaje de funciones[editar]

Una de las herramientas de perfilado más utilizada es gprof del paquete GNU binutils. Gprof usa la instrumentación de código y el muestreo para recoger un perfil funcional plano, así como un perfil de llamadas, también llamado gráfico de mariposa. Para poder perfilar, es necesario compilar nuestro código con ciertas instrucciones específicas (Para el compilador GCC existe -pg) y ejecutarlo una vez. Todo este proceso genera un archivo ilegible para nosotros con nombre ‘gmon.out’, este archivo será leído e interpretado por el propio gprof. El perfil plano obtenido contiene información acerca de los tiempos de ejecución de todas las funciones y la frecuencia con la que son llamadas.

Perfilaje basado en líneas de código.[editar]

El perfilado de funciones se vuelve inútil cuando el programa que deseamos analizar consta de funciones con gran tamaño que ocupan significativamente el tiempo de ejecución total del programa. Si la búsqueda de ‘hot spots’ dentro de nuestras funciones resulta ser extremadamente complicada, deberíamos contemplar a los perfiladores basados en líneas de código como lo puede ser OProfile. Cuando usamos OProfile solo tenemos un requisito previo, nuestros binarios deben contar con símbolos de depuración (esto usualmente se consigue con la bandera -g en nuestro compilador). De igual forma se debe iniciar un Daemon de perfilamiento que monitorizará a todo el equipo y recolectará datos de todos nuestros binarios activos. Nosotros como usuarios podemos extraer información de un binario en específico en cualquier otro momento. Esta información puede verse como un listado de fuentes anotadas en las que cada línea va acompañada del número de muestreos exitosos y del porcentaje relativo del total de los muestreos.

Sin embargo, este tipo de datos hay que tomarlos con discreción. Las tablas de símbolos generadas por el compilador deben ser consistentes para que la dirección de una instrucción de la máquina en la memoria pueda coincidir adecuadamente con la línea de origen correcta. El perfilaje basado en líneas de código es entonces una forma de perfilar que nos permite identificar los puntos calientes de un programa.

Salida generada por un profiler[editar]

La salida que puede generar un profiler depende del mismo, generalmente son las siguientes:

  • Un resumen estadístico de los eventos observados (un perfil): Un resumen de la información del perfil se muestra a menudo anotado contra las declaraciones de código fuente donde se producen los eventos, por lo que el tamaño de los datos de medición es lineal para el tamaño del código del programa.
/* ------------ source------------------------- count */
0001             IF X = "A"                     0055
0002                THEN DO
0003                  ADD 1 to XCOUNT           0032
0004                ELSE
0005             IF X = "B"                     0055
  • Una secuencia de eventos grabados (un seguimiento): Para programas secuenciales, un perfil, es generalmente suficiente, pero los problemas de performance en programas paralelos (que esperan mensajes o temas de sincronismo) a menudo dependen de la relación temporal de los acontecimientos, por lo que requieren un seguimiento completo para obtener una comprensión de lo que está sucediendo.
  • Una interacción permanente con el hipervisor (vigilancia continua o periódica a través de la visualización en pantalla por ejemplo). Esto proporciona la oportunidad de cambiar un rastro o desactivarlo en cualquier momento que desee durante la ejecución, además de ver las métricas en curso sobre el programa (en ejecución). Además proporciona la oportunidad de suspender procesos asíncronos en los puntos críticos para examinar las interacciones con otros procesos paralelos en más detalle.

Tipos de profilers basados en su salida[editar]

  • Flat profilers: Calculan el tiempo promedio de las llamadas, y no se descomponen los tiempos de llamadas basado en el destinatario o el contexto de la misma.
  • Profiler de grafo de llamadas: Muestran los tiempos de llamada y las frecuencias de las funciones, así como las cadenas de llamadas en que participan basadas en el destinatario de la llamada. En algunas herramientas de contexto completo no se conserva.

Granularidad de los datos en los diferentes tipos de profilers[editar]

Los profilers, que también son propios programas, analizar programas específicos mediante la recopilación de información sobre su ejecución. Basado en su granularidad de datos, la forma en que los profilers recopilan información, se clasifican en profilers basados en eventos o estadísticos. Ya que los profilers interrumpen la ejecución del programa para recopilar información, tienen una resolución finita en las mediciones de tiempo, los cuales se deben tomar como un subconjunto del total de información.

Profilers basados en eventos[editar]

Los lenguajes de programación que se listan a continuación poseen un profiler basado en eventos:

Profilers estadísticos[editar]

Algunos profilers operan por muestreo. un profiler por muestreo prueba el "Program counter" del programa objetivo a intervalos regulares usando interrupciones del sistema operativo. Los profilers de muestreo son típicamente menos exactos numéricamente y específicos, pero permiten que el programa de destino funcione cerca de la velocidad máxima.

Profilers instrumentadores[editar]

Algunos profilers "instrumentan" el programa objetivo con instrucciones adicionales para recopilar la información necesaria.

Instrumentar el programa puede causar cambios en el rendimiento del programa, que pueden causar resultados inexactos y heisenbugs. Instrumentar siempre tendrá algún impacto en la ejecución del programa, por lo general siempre es más lento. Sin embargo, la instrumentación puede ser muy específica y ser controlada cuidadosamente para tener un impacto mínimo. El impacto sobre un programa en particular depende de la colocación de puntos de instrumentación y el mecanismo que se utiliza para capturar la traza. El soporte de hardware para capturar la traza significa que en algunos objetivos, la instrumentación puede tener sólo una instrucción de máquina. El impacto de la instrumentación a menudo se puede deducir (es decir, eliminada por sustracción) a partir de los resultados.

gprof es un ejemplo de un profiler que utiliza tanto la instrumentación y el muestreo. La instrumentación se utiliza para recopilar información de las llamadas y los valores de tiempo real se obtienen mediante muestreo estadístico.

Instrumentación[editar]

Esta técnica añade efectivamente instrucciones al programa de destino para recopilar la información requerida.

El efecto dependerá de qué información se está recopilando, del nivel de detalles de tiempo notificados y de si se utiliza la generación de perfiles de bloques básicos junto con la instrumentación.

La instrumentación es clave para determinar el nivel de control y la cantidad de resolución de tiempo disponible para los profilers. A continuación se enumeran todas las categorías:

  • Manual:
    Realizado por el programador, por ejemplo, añadiendo instrucciones para calcular explícitamente los tiempos de ejecución, simplemente cuente eventos o llamadas a API de 
    medición, como el estándar de medición de respuesta a aplicaciones.
  • Automática a nivel de código:
    Instrumentación agregada al código fuente por una herramienta automática según una directiva de instrumentación.
  • Asistida por el compilador
  • Translación binaria:
    La herramienta agrega instrumentación a un ejecutable compilado.
  • Instrumentación en tiempo de ejecución:
    Directamente antes de la ejecución se instrumenta el código. La ejecución del programa está totalmente supervisada y controlada por la herramienta.
  • Inyección en tiempo de ejecución:
    Más ligera que la instrumentación en tiempo de ejecución. El código se modifica en tiempo de ejecución para tener saltos a funciones auxiliares.

Interpretador de instrumentación[editar]

Las opciones del intérprete de depuración permiten habilitar la recopilación de métricas de rendimiento cuando el intérprete se encuentra con cada declaración de destino. Los bytecodes, tablas de control e intérpretes JIT son tres ejemplos que suelen tener un control completo sobre la ejecución del código de destino, lo que permite la oportunidad de recolectar datos muy completos.

Hipervisor/Simulador[editar]

  • Hipervisor: Los datos se recogen mediante la ejecución del programa (por lo general) no modificada bajo un hipervisor. Por ejemplo SIMMON.
  • Simulador y Hipervisor: Los datos son recogidos de forma interactiva y selectiva mediante la ejecución del programa sin modificar en el marco de un simulador de conjunto de instrucciones. Por ejemplo IBM OLIVER y SIMON

Herramientas de Análisis de Rendimiento[editar]

Java[editar]

  • Jprofile :Es la herramienta más popular usada en aplicaciones estándar y empresariales donde las características principales son el perfilamiento de hilos, encontrar fugas de memoria y de fácil uso.
  • YourKit : Herramienta con posibilidad de realizar el perfilamiento local o remoto, automatizarlo, perfilado de CPU, memoria, hilos y más.
  • Java VisualVM: Herramienta incluida en el Java Developer Kit: (JDK) y es uno de los más sencillos de usar y permite configurar conexiones adicionales.
  • NetBeans Profiler: Conocido por su debugger, pero el IDE incluye un perfilador ligero que permite monitorear el CPU y tiempo de ejecución.

C[editar]

  • Gproof: Produce un perfil de ejecución de programas en C, Pascal o FORTRAN77, Gprof calcula la cantidad de tiempo empleado en cada rutina.  Después, estos tiempos se propagan a lo largo de los vértices del grafo de llamadas.
  • oTools: Desde Mac nos permite traducir a lenguaje ensamblador el código de C.
  • Intel VTune Profiler: De libre uso, y con perfilamiento de memoria, CPU y análisis de almacenamiento en procesadores Intel.

Python[editar]

  • cProfile: La biblioteca estándar de Python incluye este perfilador, éste rastrea el llamado de las funciones dentro del programa y genera una lista con las funciones que más tiempo tardan.
  • FunctionTrace: Simular a la herramienta anterior genera reportes de las funciones llamadas y la memoria usada por el programa en ejecución guardándolo en un JSON con posibilidad de usar el Firefox Profiler.
  • PyInstrument: Similar a cProfile pero tiene la ventaja de que en lugar de revisar cada llamada va revisando la pila de funciones cada milisegundo, por lo que es más fácil identificar que funciones tardan más en el tiempo de ejecución además de que los reportes son más concisos y fáciles de leer.

Sistemas distribuidos[editar]

Esta relación se basa directamente con el rastreo distribuido. Pero ¿Qué es el rastreo distribuido? El rastreo distribuido es un método que se utiliza para perfilar o supervisar el resultado de una solicitud que se ejecuta en un sistema distribuido.

Para obtener una visión precisa de un sistema distribuido, estas distintas métricas de nodos deben agruparse en una vista holística, esto significa que los sistemas y sus propiedades sean analizados en su conjunto y no solo a través de las partes que los componen.

Lo más común, es que las solicitudes que son enviadas a los sistemas distribuidos no se meten con todo el conjunto de los nodos del sistema, sino que solo van hacia un conjunto o ruta en concreto a través de los nodos. Para esto, el rastreo distribuido indica las rutas a las que se accede y permite que los equipos las analicen y las supervisen.

El rastreo distribuido se instala en cada nodo del sistema para permitir que los equipos pidan información acerca de los nodos como su estado y rendimiento en base a la solicitud enviada.

Este método de rastreo analiza las solicitudes de la aplicación a medida que fluyen desde los dispositivos del frontend hasta los servicios del backend y las bases de datos. Los desarrolladores pueden utilizar el rastreo distribuido para solucionar las solicitudes que presentan una alta latencia o errores.

¿Cómo funciona el rastreo distribuido?[editar]

Las aplicaciones pueden construirse como monolitos o microservicios; una aplicación monolítica se desarrolla como una única unidad funcional.

En la arquitectura de microservicios, una aplicación se descompone en servicios modulares, cada uno de los cuales manejan una función central de la aplicación y suele ser gestionado por un equipo dedicado.

Los microservicios se utilizan para construir muchas aplicaciones modernas porque facilitan la prueba y el despliegue de actualizaciones rápidas y evitan un único punto de fallo. Sin embargo, puede ser difícil solucionar los problemas de los microservicios porque a menudo se ejecutan en un backend complejo y distribuido, y las solicitudes pueden implicar secuencias de múltiples llamadas a servicios por lo que al utilizar el rastreo distribuido de end to end, los desarrolladores pueden visualizar el recorrido completo de una solicitud, desde el frontend hasta el backend, y localizar cualquier fallo de rendimiento o cuello de botella que se haya producido por el camino.

Las plataformas de rastreo distribuido de end to end comienzan a recopilar datos en el momento en que se inicia una solicitud, como cuando un usuario envía un formulario en un sitio web. Esto desencadena la creación de un ID de rastreo único y un tramo inicial, llamado tramo padre, en la plataforma de rastreo.

Un rastreo representa toda la ruta de ejecución de la solicitud, y cada tramo en el rastreo representa una sola unidad de trabajo durante ese viaje, como una llamada a la API o una consulta a la base de datos. Cada vez que la solicitud entra en un servicio, se crea un tramo hijo de nivel superior. Si la solicitud realiza múltiples comandos o consultas dentro del mismo servicio, el tramo hijo de nivel superior puede actuar como padre de otros tramos hijos anidados debajo de él.

La plataforma de rastreo distribuido codifica cada tramo hijo con el ID de rastreo original y un ID de tramo único, datos de duración y error, y metadatos relevantes, como el ID del cliente o la ubicación.

Finalmente, todos los tramos se visualizan en un graph flame, con el tramo padre en la parte superior y los tramos hijos anidados debajo en orden de aparición.

Dado que cada tramo está cronometrado, los ingenieros pueden ver cuánto tiempo pasó la solicitud en cada servicio o base de datos, y priorizar sus esfuerzos de solución de problemas en consecuencia. Los desarrolladores también pueden utilizar el graph flame para determinar qué llamadas presentan errores.

Beneficios y desafíos del rastreo distribuido.[editar]

Los ingenieros de frontend, backend y de fiabilidad del sitio utilizan el rastreo distribuido para lograr los siguientes beneficios:

  • Reducir el MTTD (tiempo medio de detección) y el MTTR (tiempo medio de reparación, resolución, respuesta o recuperación: Si un cliente informa de que una función de una aplicación es lenta o no funciona, el equipo de soporte puede revisar el rastreo distribuido para determinar si se trata de un problema de backend.
  • Los ingenieros pueden entonces analizar el rastreo generado por el servicio afectado para solucionar rápidamente el problema. Si utiliza una herramienta de rastreo distribuido end to end, también podrá investigar los problemas de rendimiento del frontend desde la misma plataforma.
  • Comprender las relaciones de los servicios: Al ver el rastreo distribuido, los desarrolladores pueden entender las relaciones causa-efecto entre los servicios y optimizar su rendimiento. Por ejemplo, la visualización de un tramo generado por una llamada a la base de datos puede revelar que la adición de una nueva entrada en la base de datos provoca latencia en un servicio ascendente.
  • Medir acciones específicas de los usuarios: El rastreo distribuido ayuda a medir el tiempo que se tarda en completar acciones clave del usuario, como la compra de un artículo. El rastreo puede ayudar a identificar los cuellos de botella y los errores del backend que están perjudicando la experiencia del usuario.
  • Mejorar la colaboración y la productividad: En las arquitecturas de microservicios, diferentes equipos pueden ser propietarios de los servicios que intervienen en la realización de una solicitud. El rastreo distribuido deja claro dónde se ha producido un error y qué equipo es responsable de solucionarlo.
  • Mantener los Acuerdos de Nivel de Servicio (SLA): La mayoría de las organizaciones tienen acuerdos de nivel de servicio, que son contratos con los clientes u otros equipos internos para cumplir los objetivos de rendimiento. Las herramientas de rastreo distribuido agregan datos de rendimiento de servicios específicos, de modo que los equipos pueden evaluar fácilmente si están cumpliendo los SLA.

A pesar de estas ventajas, existen algunos retos asociados a la implementación del rastreo distribuido:

  • Instrumentación manual: Algunas plataformas de rastreo distribuido requieren que se instrumente o modifique manualmente el código para iniciar el rastreo de las solicitudes. La instrumentación manual consume un valioso tiempo de ingeniería y puede introducir errores en su aplicación, pero la necesidad de realizarla suele estar determinada por el lenguaje o el marco de trabajo que se desea instrumentar. La estandarización de las partes de su código a instrumentar también puede hacer que se pierdan los rastreos.
  • Muestreo basado en el cabezal: Las plataformas de rastreo tradicionales tienden a muestrear aleatoriamente el rastreo justo cuando comienza cada solicitud. Este enfoque hace que se pierdan rastros o que sean incompletos, con el muestreo basado en cabezas, las empresas no siempre pueden capturar los rastreos que son más relevantes para ellas, como las transacciones de alto valor o las solicitudes de los clientes empresariales.

Por el contrario, algunas plataformas modernas pueden ingerir todos sus rastros y basarse en decisiones basadas en la cola, lo que le permite capturar rastros completos que están etiquetados con atributos relevantes para el negocio, como el ID del cliente o la región.

  • Cobertura sólo de backend: A menos que utilice una plataforma de rastreo distribuido de end to end, se genera un ID de rastreo para una solicitud sólo cuando llega al primer servicio de backend. No tendrá visibilidad de la sesión de usuario correspondiente en el frontend.

Esto hace que sea más difícil determinar la causa raíz de una solicitud problemática y si un equipo del frontend o del backend debe solucionar el problema.

Un sistema de alto rendimiento puede generar millones de trazos por minuto, lo que dificulta la identificación y supervisión de los trazos más relevantes para sus aplicaciones. Afortunadamente, existen herramientas que te ayudan a sacar a la luz los datos de rendimiento más útiles.

Véase también[editar]

Referencias[editar]

  1. Descripción de cursos: Profiling y Tunning en Java. IBM. [1] Archivado el 4 de octubre de 2013 en Wayback Machine.
  2. a b Funcionalidad implementada por profilers. [2]
  3. Profiling. Estratecgias. [3]
  4. Computerworld 19 de septiembre de 1977
  5. gprof: a Call Graph Execution Profiler // Proceedings of the SIGPLAN '82 Symposium on Compiler Construction, SIGPLAN Notices, Vol. 17, No 6, pp. 120-126; doi:10.1145/800230.806987
  6. Amitabh Srivastava and Alan Eustace, "Atom: A system for building customized program analysis tools", 1994 (download) // Proceeding PLDI '94 Proceedings of the ACM SIGPLAN 1994 conference on Programming language design and implementation. Pages 196 - 205, doi:10.1145/773473.178260
  7. 20 Years of PLDI (1979–1999): A Selection, Kathryn S. McKinley, Editor

Enlaces externos[editar]