Análisis de rendimiento de software
En ingeniería de software el análisis de rendimiento, comúnmente llamado profiling o perfilaje, son herramientas de software utilizadas para analizar y medir el rendimiento de una aplicación o programa. Su objetivo principal es identificar los puntos críticos de la aplicación que consumen la mayor cantidad de tiempo de ejecución o recursos del sistema.
Los perfiladores pueden ayudar a los desarrolladores a identificar cuellos de botella, áreas de código ineficientes o subóptimas y ofrecer información detallada sobre el tiempo de ejecución y el uso de recursos, como la cantidad de tiempo de CPU utilizado, la memoria asignada y los eventos de E/S. Esto permite a los desarrolladores optimizar el rendimiento de sus aplicaciones al realizar cambios específicos en el código o en la configuración del sistema.
Existen diferentes tipos de perfiladores, como los perfiladores de CPU, que miden el tiempo de ejecución de cada función o método dentro de una aplicación y muestran la cantidad de tiempo que se consume en cada una. También hay perfiladores de memoria, que rastrean el uso de la memoria y pueden ayudar a identificar fugas de memoria o áreas donde se está utilizando una cantidad excesiva de memoria.
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.[1]
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
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.[3] 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.[4]
En 1994, Amitabh Srivastava y Alan Eustace de Digital Equipment Corporation publican un artículo describiendo ATOM.[5] 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.[6]
Los perfiladores, en el ámbito de la computación, tienen una larga historia de desarrollo y evolución que se remonta a las primeras etapas de la informática. A continuación, menciona los los hitos importantes en la historia de los perfiladores:
- Años 1960: En esta época temprana de la computación, los sistemas eran limitados en recursos y capacidad de procesamiento. Los primeros perfiles de rendimiento se basaban principalmente en la observación y la intuición del programador para identificar áreas de código ineficientes.
- Años 1970: Con el crecimiento de la capacidad de procesamiento y la complejidad de los sistemas informáticos, surgieron las primeras herramientas de perfilado automatizadas. Estas herramientas permitían a los programadores realizar mediciones y análisis más precisos del rendimiento de sus programas.
- Años 1980: Con la aparición de lenguajes de programación más avanzados y sistemas operativos más sofisticados, los perfiladores comenzaron a proporcionar información más detallada, como el tiempo de ejecución de cada función o método.
- Años 1990: Con el auge de la programación orientada a objetos y la explosión de la web, los perfiladores se adaptaron para manejar sistemas más complejos y aplicaciones distribuidas. Surgieron herramientas más sofisticadas que permitían el análisis del rendimiento en entornos multiplataforma.
- Años 2000: Con el crecimiento exponencial de las aplicaciones web y móviles, los perfiladores se volvieron aún más importantes. Se desarrollaron herramientas específicas para optimizar el rendimiento en entornos web y móviles, lo que permitió a los desarrolladores analizar el rendimiento de sus aplicaciones en diferentes dispositivos y navegadores.
- Actualidad: En la actualidad, los perfiladores continúan evolucionando para satisfacer las necesidades de las aplicaciones modernas y los sistemas informáticos cada vez más complejos. Se han desarrollado herramientas avanzadas que pueden analizar el rendimiento en tiempo real, identificar cuellos de botella y sugerir mejoras automáticas para optimizar el rendimiento.
Los perfiladores han experimentado un desarrollo continuo a lo largo de la historia de la computación, adaptándose a medida que los sistemas y las aplicaciones se volvieron más complejos. Han pasado de depender de la intuición del programador a utilizar herramientas automatizadas y sofisticadas para medir y optimizar el rendimiento de las aplicaciones.
Recopilación de los eventos del programa
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.
Aparte delo anterior, existen de igual manera herramientas para este fin, las herramientas de perfilado son herramientas de software que se utilizan para analizar y medir el rendimiento de una aplicación o sistema informático. Estas herramientas permiten a los desarrolladores identificar cuellos de botella en el código y mejorar el rendimiento de una aplicación. Algunas herramientas populares de perfilado incluyen:
- Profiler de CPU: Herramienta que mide el tiempo que tarda una aplicación en realizar una determinada tarea y el uso de la CPU durante ese tiempo.
- Profiler de memoria: Herramienta que mide el uso de la memoria de una aplicación y detecta fugas de memoria.
- Profiler de E/S: Herramienta que mide el tiempo que tarda una aplicación en realizar operaciones de entrada y salida.
- Profiler de red: Herramienta que mide el tiempo que tarda una aplicación en enviar y recibir datos a través de una red.
- Profiler de rendimiento web: Herramienta que mide el tiempo de carga de una página web y detecta cuellos de botella en el código.
Tipos de perfilaje
Ya que el objetivo de la perfilación es obtener información puntual sobre los aspectos de ejercicio de un programa, ésta depende en gran medida de la estructura del mismo, y que se sigan buenas prácticas de programación para que la información obtenida a partir de estas recolecciones sea significativa. Estas recolecciones de datos pueden hacerse de forma manual o automática; dependiendo el tamaño del proyecto o la naturaleza de la información que será recolectada. Para ambos tipos de recolección hay métodos específicos que pueden ser implementados.
Perfilación Manual
La recolección manual de datos implica la inclusión de instrucciones en el código que se encarguen de recolectar los datos en el código que nos permitan conocer su eficiencia. Esto puede hacerse usando funciones nativas del lenguaje, mediante el uso de bibliotecas especializadas o consultas a APIs. Este método puede resultar más efectivo cuando ya se ha identificado previamente un hotspot y ahora se busca extraer información más puntual sobre su comportamiento para identificar las áreas donde puede ser optimizado. Algunos métodos de perfilación manual son:
- Marcadores de tiempo: Se colocan marcadores de tiempo al inicio y final de una sección de código, o llamada a función, para obtener el total de tiempo que toma su ejecución. Si dentro de esta función se hace uso de otras, esto afectará el tiempo de la misma; por lo que la interdependencia de funciones no optimizadas puede suponer un obstáculo para la medición efectiva.
- Contadores: A diferencia de los marcadores de tiempo, un contador es utilizado para registrar cuántas veces es llamada una función dentro de la ejecución de un programa, conocer esto puede ser un primer paso cuando se comienza a buscar hotspots; una función que es llamada constantemente puede ocasionar cuellos de botella si no se encuentra bien optimizada. En casos donde la sustitución del cuerpo de la función por su llamado es posible, el tiempo de ejecución puede ser disminuido.
- Registro de eventos: Guarda información sobre la ocurrencia de acciones específicas dentro de la ejecución de un programa, como la modificación de una variable o la llamada a una función. Conocer el contexto de la ocurrencia de un cambio puede ser de ayuda al determinar cómo este puede mejorado; como en la estructuras if-else, donde la opción de mayor uso debería colocarse al inicio de la condicional, para evitar la mayor cantidad de comparaciones innecesarias.
Perfilación Automática.
El perfilado automático hace uso de software especializado y herramientas nativas del sistema operativo, o compiladores, para recolectar datos sobre el rendimiento de un programa de forma automatizada; por lo que no es necesario hacer modificaciones manuales en el código para obtener las métricas usando estos métodos. Algunos de los métodos para los que son usados son:
- Profiling profilers: Se ejecutan a la par del programa y almacenan información referente a la ejecución.
- Análisis de memoria: Se trata de sistemas que monitorean el uso de la memoria con el fin de detectar comportamientos inusuales derivados de un mal manejo de la misma.
- Análisis de rendimiento web: Especialmente útil en aplicaciones cuya funcionalidad depende de la red; pueden registrar los tiempos de carga de las páginas, así como el tiempo de resolución de las solicitudes.
Perfilaje basado en líneas de código.
El perfilado de funciones es limitado cuando el programa contiene funciones extensas que ocupan la mayor parte del tiempo de ejecución. En estos casos, los perfiladores basados en líneas de código, como OProfile, son más útiles. Estos perfiladores requieren que los binarios tengan símbolos de depuración y se ejecuta un Daemon para recopilar datos de todos los binarios activos. Podemos extraer información de un binario específico en cualquier momento, mostrando las líneas de código con el número de muestreos y su porcentaje relativo. Sin embargo, debemos tener precaución con estos datos, ya que requieren coherencia en las tablas de símbolos generadas por el compilador para que coincidan correctamente con las instrucciones de la máquina en la memoria. El perfilado basado en líneas de código nos ayuda a identificar los puntos críticos de un programa llaman demasiadas veces. Al corregir estos problemas de diseño, se pueden mejorar significativamente el rendimiento del programa. Es importante tener en cuenta que el perfilado basado en líneas puede ser una técnica detallada y que requiere mucho tiempo y recursos.
Perfilajes basados en líneas
El perfilado basado en líneas es una técnica de análisis de rendimiento de programas de computadora que consiste en medir el tiempo de ejecución de cada línea de código individualmente. Para hacer esto, se utilizan herramientas de perfilado que generan un informe detallado que muestra cuánto tiempo se tarda en ejecutar cada línea de código en el programa. Esta técnica es útil porque permite a los desarrolladores identificar las líneas de código específicas que están causando cuellos de botella o problemas de rendimiento en el programa. Al analizar el informe generado por el perfilado basado en líneas, los desarrolladores pueden ver claramente las líneas de código que están consumiendo la mayor cantidad de tiempo de CPU y, por lo tanto, son los principales contribuyentes a los problemas de rendimiento. Además, el perfilado basado en líneas puede ayudar a los desarrolladores a identificar problemas específicos de diseño de código, como bucles anidados innecesarios, operaciones costosas o funciones que se
Perfilajes basados en el sentido común
- Trabajar menos, es decir, reorganizar el código para realizar menos trabajo siempre que sea posible.
- Evitar operaciones costosas, como las funciones trigonométricas o la exponenciación, y buscar alternativas más baratas. También es importante reducir el conjunto de trabajo del código, es decir, la cantidad de memoria que usa, para aumentar la probabilidad de aciertos en la memoria caché.
- Eliminación de subexpresiones comunes es una técnica que busca ahorrar tiempo pre calculando partes de expresiones complejas y asignándolas a variables temporales antes de que comience una construcción de código que usa esas partes varias veces. En el caso de bucles, esta optimización también se denomina bucle de movimiento de código invariable.
- Evitar ramas es otra técnica para mejorar el rendimiento. Los bucles "estrechos", es decir, los bucles que tienen pocas operaciones son candidatos típicos para canalización de software, desarrollo de bucles y otras técnicas de optimización. Si por alguna razón la optimización del compilador falla o es ineficiente, el rendimiento se verá afectado. Esto puede suceder fácilmente si el cuerpo del bucle contiene ramas condicionales.
Perfilaje de funciones
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.
Salida generada por un profiler
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
- 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.
- Profiler sensible a la entrada: Los perfiladores sensibles a la entrada añaden una dimensión adicional a los perfiladores planos o de grafo de llamadas al relacionar las medidas de rendimiento con características de las cargas de trabajo de entrada, como el tamaño de entrada o los valores de entrada. Generan gráficos que caracterizan cómo escala el rendimiento de una aplicación en función de su entrada.[7]
Granularidad de los datos en los diferentes tipos de profilers
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 de códigos
Siendo los perfiladores más populares, estos nos ayudan a poder realizar el análisis de toda la información recopilada sobre la ejecución y funcionamiento de un programa, como el tiempo de ejecución del programa o la cantidad de recursos utilizados para su funcionamiento; esto también aplica para partes específicas del programa (ej. las funciones del programa o sus variables utilizadas). Gracias a estos perfiladores, obtendremos un análisis detallado del rendimiento del código del programa, lo cual nos permitirá poder optimizarlo y tomar las medidas necesarias para mejorar su desempeño o de identificar errores de rendimiento en caso de necesitarlo.
Profilers en tiempo real
Existen herramientas de profiling que nos permiten analizar software en tiempo real, compilando los datos relevantes de rendimiento al mismo tiempo que el programa es ejectuado, dándonos un desglose de la información que permite identificar los posibles errores de este, facilitando la solución de problemas para el desarrollador.
Profilers en la nube
Gracias al avance de la tecnología en la actualidad, los profilers de la nube son una realidad y estos funcionan justo como un perfilador común, la diferencia es que estos están orientados específicamente al análisis de aplicaciones y software que se ejecute en la nube. Brindando ya no solo un análisis de programas, también un análisis orientado a la red y servidores que alojen estos programas.
Profilers basados en eventos
Los lenguajes de programación que se listan a continuación poseen un profiler basado en eventos:
- Java (lenguaje de programación) (Java Virtual Machine Tools Interface)
- Microsoft .NET
- Python
- Ruby
Profilers estadísticos
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
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
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
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.
Optimizaciones en C++
Las optimizaciones también son específicas conforme al lenguaje de programación. Estas optimizaciones incluyentécnicas estándar como cuenta de referencias, copy-on-write, apuntadores pequeños, etc., pero las optimizaciones que se pueden realizar en C++ son muchas más. Comúnmente se cree que el compilador de C++ es capaz de ver a través de todas las abstracciones y ofuscaciones que un programa avanzado puede llegar a tener, pero C++ debe verse más como un lenguaje que permite el manejo de complejidad. A continuación, se presentan algunas optimizaciones a los bugs de rendimiento más comunes y conceptos erróneos e programas de C++, con enfoque en los ciclos de bajo nivel.
- Temporales: En C++ se cuenta con un estilo implícito de programación en el que mecanismos automáticos
ocultan complejidad al programador. Un problema frecuente ocurre con expresiones que contienen cadenas con operadores sobrecargados, de manera que el código realice muchos más pasos de los que se ve a simple vista. El uso de variables temporales puede mejorar el rendimiento del programa debido a que usar temporales en el programa principal puede disminuir el uso de temporales adicionales en la optimización del compilador. No obstante, esto no es tan evidente en funciones estándar. Los compiladores en C++ son bastante buenos en la implementación de Inlining, lo cual puede hacer más difícil apreciar el uso de lastemporales, pero el objetivo de este proceso no es optimizar código, sino rectificar las sanciones más mseveras en el funcionamiento del código, generadas por las especificaciones del lenguaje. Es así que el proceso de Inlining puede ocultar el uso de temporales y caché que se deben revisar. En estos casos deshabilitar el Inlining puede ser útil, pero se debe de considerar que los resultados se distorsionarán.
- Manejo de memoria dinámica: El manejo de memoria dinámica llega a ser un problema debido a la asignación y liberación constante de memoria. Cada que se utiliza memoria dinámica, el compilador no es capaz de saber desde un principio el tamaño de la variable y cada que se ocupe un nuevo espacio de memoria se deben de llamar a las funciones responsables de asignación y liberación. Esto afecta al desempeño del programa, pues las funciones de la librería estándar no están optimizadas del todo. Para reducir el uso de asignación y liberación, se necesita monitorear el uso de temporales. Además, existen dos estrategias que pueden ayudar: Lazy construction y static constuction. La estrategia de lazy construction o construcción floja consiste en llamar a la construcción de un objeto hasta que esta sea necesaria en vez de al principio delprograma como en el lenguaje C. Por otro lado, la estrategia static construction o construcción estática consiste en llamar a la construcción de un objeto fuera de un ciclo o un bloque y marcarla como estática, de manera que sea accesible en cualquier parte del programa. Esta última estrategia es especialmente útil cuando el objeto se utiliza frecuentemente en el
programa o en casos en los que la asignación dememoria es más rápida que la reasignación.
- Kernel e iteradores de ciclo: Los ciclos son comúnmente utilizados, especialmente en código con aplicaciones científicas, pero la habilidad del compilador para optimizarlos es pivotal. De esta manera, código que parece sencillo genera en realidad muchas complicaciones para el compilador, especialmente en casos de operadores con índices sobrecargados. Para evitar esto, es conveniente utilizar iteradores en lugar de métodos para el acceso a datos, lo cual puede optimizar el código en C++ a gran medida. Adicionalmente y si es posible, los ciclos a bajo nivel deben de residir en unidades de compilación separadas y los iteradores deben de pasarse como apuntadores. Esto minimiza la interferencia del compilador con la vista a alto nivel.
Hipervisor/Simulador
- 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
Metodologías
Para poder realizar un perfilado adecuado sobre nuestro hardware debemos de centrarnos en temas relacionados al tiempo de ejecución (es decir, el reloj). En estos, la única información requerida es saber que recursos se encuentran limitados, por qué se encuentran limitados y si los ticks del reloj son insuficientes. Hoy en día contamos con pocos obstáculos en el rendimiento dentro de nuestras computadoras, ya que los registros de los chips se encuentran especializados para cualquier eventualidad y pueden modificarse para lograr resolver cualquier tipo de problema si se llegase a necesitar. Sin embargo, algunos de los eventos que podemos notar son los siguientes:
- Número de transacciones en el bus (transferencias de renglones en el caché): Aunque ocurren fallas en el caché, que suceden cuando una aplicación solicita información del caché que no se encuentra actualmente ahí, los mecanismos de perfilado nos ayudarán a bajar la probabilidad de su ocurrencia.
- Número de cargas y guardados: Junto con las transacciones de bus nos darán una indicación sobre qué tan eficientemente se guardan los renglones de información. Se debe tener en cuenta el uso de los pipelines y si estos se encuentran ‘detenidos’ una cantidad de tiempo o si muchas operaciones matemáticas se están realizando en datos fuera de los registros, ya que esto puede ser un indicador de que puede no ser un error en la eficiencia.
- Número de operaciones con punto flotante: Frecuentemente se sobrestima la importancia de esta métrica; sin embargo, si la cantidad de operaciones realizadas por cada ciclo de reloj está cerca del máximo prestablecido es poco probable que se pueda realizar alguna modificación para la eficiencia.
- Ramificaciones mal predichas: Cuando la CPU predice la salida de una rama condicional y el resultado es distinto a la predicción, puede que resulte en varias decenas de ciclos de reloj solucionando este problema.
- Estancamiento en el pipeline: Existen eventos donde dependencias relacionadas al procesador no se encuentran ocupadas dentro del pipeline. Esto quiere decir que eventos donde se encuentra en pausa ya que está limitada por el ancho de banda o la unidad aritmética que se encuentran esperando por datos no pueden ser optimizados. Es imposible modificar el hardware para solucionar estos problemas ya que se requiere de una solución para la ejecución de instrucciones que aparecen hasta después dentro del código más sus variables se encuentran disponibles en aquel momento.
- Número de instrucciones ejecutadas: Junto con los ciclos de reloj, esto nos ayuda a juzgar que tan escalar es nuestro hardware a comparación con las múltiples unidades de ejecución utilizadas. Usualmente, el ciclo de reloj es capaz de realizar 2-3 instrucciones por ciclo (a las que tiene acceso el código), aún con varias optimizaciones en el pípeline.
Algunas optimizaciones para estos problemas podrían parecer de sentido común, más estos simples cambios dentro del código frecuentemente resultan en un aumento en la eficiencia. Algunos de estos cambios son:
- Reacomodación del código para que se realice menos trabajo (como puede ser en ciclos for o ramificaciones if).
- Reducción en la fuerza, donde se optimiza para que no se utilicen muchas funciones de alta complejidad o ambigüedad donde se calcula espacio más grande del necesario.
- Disminuir el espacio de memoria utilizado por el código, permitiendo que el caché cuente con más recursos.
- Eliminación de subexpresiones comunes. Podemos ‘pre-calcular’ ciertas operaciones que solo se construirán en momentos futuros, como dentro de algún ciclo. Al solo tener que llamarlas en el futuro, logramos un movimiento de código invariable en el ciclo.
- Evitar ramificaciones, donde tenemos pocos operandos o funciones siendo realizadas o varios condicionales.
- Uso de sets de instrucciones SIMD, donde se realiza una vectorización. Un ciclo vectorizable correrá más rápido si más operaciones se realizan dentro de una misma instrucción, es decir, el tamaño de los datos debe ser lo más pequeño posible. Se utiliza una transformación de los ciclos, llamada “loop unrolling”, donde se pretende optimizar la ejecución del programa al eliminar o reducir el número de iteraciones. Visiblemente, el código se verá más grande y ocupará mayor espacio, más reducirá el número de comparaciones y la necesidad de revisar los valores de las variables o realizar y checar los incrementos dentro de los ciclos.
Los compiladores juegan un gran papel en la perfilación. Cada compilador moderno incluye interruptores de línea de comando, los cuales permiten el acceso a diferentes opciones de optimización. Sin embargo, es importante tomar en cuenta que el compilador ya tiene un trabajo extremadamente complejo al mapear el código fuente escrito en lenguaje de alto nivel a lenguaje máquina y utilizar los recursos del procesador de la mejor forma posible. Debido a su trabajo y su funcionamiento, los compiladores pueden ser sumamente eficientes y fallar en tarea que los programadores consideran como simples, de manera que asumir que el compilador será capaz de realizar todas las tareas conforme a la eficiencia es algo equivocado. Es así que es necesario que los programadores tengan en cuenta las siguientes características de optimización en C y C++.
- Opciones generales de optimización: Cada compilador tiene una serie de opciones estándar para la optimización, las cuales se encuentran documentadas en los manuales. No obstante, todos los compiladores se abstienen de la mayoría de optimizaciones al más bajo nivel, por lo que lo correcto es el análisis a través del debugger. A alto nivel la optimización de compiladores consiste en combinar líneas fuente, detectar y eliminar variables redundantes, reacomodar expresiones aritméticas, etc., pero algunos problemas suelen presentarse en la optimización de los niveles más bajos. Esto puede indicar un defecto en el compilador o un típico bug como una violación en los límites de un arreglo, pues el compilador puede tomar que inofensivo a bajo nivel debido a la diferencia en el acomodamiento.
- Inlining: El proceso de Inlining intenta ahorrar gastos generales insertando código completo de una función o subrutina en una dirección específica desde donde se llama. Mientras que los datos de la función aún se tienen que establecer cada que esta es llamada, Inlining elimina la necesidad de insertar argumentos en el stack y le permite al compilador usar registros conforme los considere necesarios, reduciendo así la presión en los registros. La presión en los registros ocurre cuando el CPU no tiene registros suficientes para guardar todos los operandos necesarios dentro de una computación compleja o un ciclo. Asimismo, el Inlining de una función permite que el compilador vea una porción más larga de código y probablemente implementar más optimizaciones que no serían posibles de otra manera. Sin embargo, en situaciones de rendimiento crítico, ofuscar al compilador con una vista demasiado amplia puede ser contraproducente. Debido a su funcionamiento puede haber malentendidos conforme al tipo de funciones en las que es conveniente implementar Inlining, pues, contrario a lo que se creería, las funciones pequeñas son las que más potencial tienen con este proceso. En C++, Inlining es esencial para un buen rendimiento, pues operadores que se sobrecargan por tipos simples de datos tienden a ser funciones pequeñas y copias temporales pueden ser evitadas si una función con Inlining devuelve un objeto. El Inlining se controla de manera automática en el controlador del compilador y puede resultar sumamente eficiente, pero realizar Inlining a una función en varios lugares puede hacer mucho más grande el código de objetos, lo cual conlleva a problemas con el caché. Si las instrucciones que pertenecen a un loop no se pueden guardar en el caché, compiten por transferencias de datos hacia o desde el caché externo o la memoria principal, lo cual aumenta el tiempo de ejecución.
- Aliasing: El compilador debe de realizar diversas suposiciones, las cuales pueden limitar su habilidad de generar código en lenguaje máquina de manera óptima. En el caso de C y C++ los estándares permiten un Aliasing arbitrario de los apuntadores, lo cual, en esencia, consiste en asignarles un alias. Esto se debe a que el compilador debe suponer que es posible que la información se superponga, de manera que es necesario guardar cada valor en el orden en el que aparezca. Si bien el proceso de Aliasing puede ser conveniente, los programas en C y C++ tienen a ser más lentos debido a esto. Todos los compiladores de C y C++ tienen opciones para el control en el nivel de Aliasing que el compilador tiene permitido asumir, de forma que, si se le dice al compilador que el Aliasing no es necesario, el código puede optimizarse de una manera más directa. No obstante, si el programador establece que el Aliasing no es necesario en un caso donde sí lo es, es muy probable que la función produzca resultados erróneos.
- Precisión computacional: Los compiladores en ocasiones se abstienen de reacomodar expresiones aritméticas si es necesario aplicar reglas asociativas, excepto si están activadas las optimizaciones “agresivas”. Esto pues el compilador prioriza un código óptimo a la manutención de la precisión, de manera que el programador debe de decidir si se deben de reagrupar las expresiones de manera manual. Los compiladores modernos tienen opciones en la línea de comandos para limitar el reacomodamiento de expresiones aritméticas, incluso a niveles de alta optimización. Adicionalmente, se deben de tomar en cuenta valores denormales como, por ejemplo, un número flotante más pequeño al mínimo permitido. En estos casos, si la pequeña pérdida de precisión es tolerable, el valor debe de ser tratado como 0.
- Optimización de registros: El compilador siempre intenta almacenar los operadores más frecuentemente utilizados en registros y mantenerlos ahí tanto como pueda, mientras que esto sea factible. Inlining ayuda a la optimización de registros debido a que permite que el optimizador almacene valores en registros que de otra manera hubieran pasado a memoria antes de la llamada de la función. No obstante, ciclos que contienen muchas variables y expresiones aritméticas hacen más difícil la optimización para el compilador, pues el número de registros para el almacenamiento de enteros y números flotantes es limitado de manera estricta. En el caso en el que ya no haya registros, las variables se tienen que dividir, lo cual puede reduce el peso de ejecución un poco. En algunos casos incluso resulta conveniente dividir un ciclo para reducir lapresión en los registros. La división también puede apoyarse a través de hardware.
- Utilización de registros de compilador: Es bastante fácil ocultar información crucial para el compilador cuando se está escribiendo código, forzando al compilador a dejar de lado optimización posible en los primeros pasos. Muchos compiladores ofrecen la opción de generar listados de código fuente anotados o al menos registros que describan detalles de las optimizaciones que fueron implementadas. Información sobre el uso y desbordamiento de registros, los factores de ejecución y pipelining de software, el uso de instrucciones SIMD y las suposiciones del compilador sobre el tamaño de los ciclos son bastante valiosos para juzgar la calidad del código en lenguaje máquina generado. Desgraciadamente, no todos los compiladores tienen la habilidad de ofrecer anotaciones tan comprensibles y los programadores en muchas ocasiones deben de interpretar. También existe la opción de inspeccionar manuela mente el código en lenguaje ensamblador que fue generado (todos los compiladores ofrecen la opción de generar en código en ensamblador en vez de un archivo de objeto vinculable), pero analizar el código fuente original conlleva una gran cantidad de trabajo y experiencia.
Herramientas de Perfilado
Son herramientas de software que se utilizan para analizar y medir el rendimiento de una aplicación o sistema informático. Estas herramientas permiten a los desarrolladores identificar cuellos de botella en el código y mejorar el rendimiento de una aplicación.
Algunas herramientas populares de perfilado incluyen:
- Profiler de CPU: Herramienta que mide el tiempo que tarda una aplicación en realizar una determinada tarea y el uso de la CPU durante ese tiempo.
- Profiler de memoria: Herramienta que mide el uso de la memoria de una aplicación y detecta fugas de memoria.
- Profiler de E/S: Herramienta que mide el tiempo que tarda una aplicación en realizar operaciones de entrada y salida.
- Profiler de red: Herramienta que mide el tiempo que tarda una aplicación en enviar y recibir datos a través de una red.
- Profiler de rendimiento web: Herramienta que mide el tiempo de carga de una página web y detecta cuellos de botella en el código.
A continuación se listan algunas herramientas de perfilado, de acuerdo al lenguaje de programación que se utilice:
Java
- Jprofiler
Es la herramienta más popular usada en aplicaciones estándar y empresariales ya que cuenta con una interfaz de usuario muy intuitiva, las características principales son el perfilamiento de hilos, encontrar fugas de memoria y resolver cuellos de botella. Es compatible con plataformas Windows, MacOs, Linux, FreeBSD, Solaris, AIX y HP-UX.
- YourKit
Esta herramienta se utiliza para analizar el rendimiento de aplicaciones java en desarrollo y producción, cuenta con funciones básicas para visualizar hilos, recolecciones de basura, uso de memoria y fugas de memoria, con soporte para perfiles locales y remotos. También es útil para la creación de perfiles de CPU, que permiten la creación de perfiles enfocados en ciertas áreas de nuestro código, análisis de E/S y de multithreading. Es compatible con plataformas Windows, MacOs, lINUX, etc.
- Java VisualVM
A diferencia de las anteriores, esta herramienta es gratuita y está incluida en el Java Developer Kit: (JDK), Java VisualVM proporciona un conjunto de herramientas de análisis de rendimiento que incluyen monitoreo de memoria, análisis de CPU, análisis de hilos y un profiler de java.
- NetBeans Profiler
Se incluye con el Netbeans IDE de código abierto de Oracle y es una buena opción cuando queremos que todo éste incluido en un solo programa (IDE+Profiler), es mayormente conocido por su debugger, pero el IDE incluye un perfilador ligero que permite monitorear el CPU y tiempo de ejecución.
- IntelliJ Profiler
Esta herramienta es útil para la creación de perfiles de asignación de CPU y memoria, nos permite comenzar con unos pocos clics sin ningún tipo de configuración, además de que nos permite evaluar visualmente el rendimiento de diferentes enfoques y obtener información sobre las operaciones en tiempo de ejecución de manera rápida y eficiente.
C
- Gproof
Es una herramienta de análisis de rendimiento que 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.
- Intel VTune Profiler
Es una herramienta de análisis de rendimiento para plataformas x86 y x64 que proporciona una amplia variedad de información sobre el rendimiento del sistema, es de libre uso y con perfilamiento de memoria, CPU y análisis de almacenamiento en procesadores Intel.
- oTools
Desde Mac nos permite traducir a lenguaje ensamblador el código de C.
- Perf
Es una herramienta de perfilado de rendimiento de sistema en Linux que se utiliza para analizar la utilización de la CPU y la memoria, y para medir el rendimiento de aplicaciones.
- Valgrind
Es una herramienta de depuración y perfilado de memoria que ayuda a detectar problemas de memoria, como fugas de memoria y errores de acceso.
Python
- cProfile Viene incluido en la biblioteca estándar de python, éste rastrea el llamado de las funciones dentro del programa y genera una lista con las funciones que más tiempo tardan. Tiene tres puntos fuertes que son:
- Que se incluye con la biblioteca estándar.
- Que perfila una serie de estadísticas diferentes sobre el comportamiento de las llamadas, esto permite determinar si una función es lenta por sí misma o si llama a otras funciones que son lentas.
- Que puede restringir cProfile libremente, es decir, mostrar toda la ejecución de un programa o activar la creación de perfiles solo cuando se ejecuta la función seleccionada.
- 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.
- Line Profiler
Es una herramienta de perfilado que nos permite medir el tiempo de ejecución de cada línea de código en las funciones, además esta herramienta nos permite detectar los cuellos de botella de rendimiento y optimizar el código para mejorar el rendimiento.
- PyInstrument
Similar a cProfile pero este nos brinda un informe de aquella parte del código que toma más tiempo, las principales diferencias son 2:
- Que muestrea la pila de llamadas del programa cada milisegundo, con lo que podemos detectar lo que está consumiendo la mayor parte del tiempo de ejecución.
- Que nos da informes más concisos, pues nos muestra las funciones principales del programa que consumen más tiempo.
- Pyflame
Es una herramienta de perfilado que nos permite medir el tiempo de ejecución de cada función en el código de python. Es una herramienta de bajo impacto que no afecta significativamente el rendimiento de la aplicación.
Otras herramientas de perfilado
- tprof
En el sistema operativo AIX, el programa de utilidad tprof es la principal fuente de datos de perfilado de bajo nivel. tprof se utiliza para perfilar dónde se está empleando el tiempo de ejecución de procesador en la aplicación.
El programa de utilidad tprof funciona de la siguiente manera:
- Cada 10 milisegundos, tprof toma nota del punto en ejecución en ese momento en el binario de la aplicación.
- Cada punto anotado se denomina una "marca". La distribución de estas marcas, a lo largo del tiempo de vida de la ejecución de la aplicación, indica aproximadamente cuánto tiempo de procesador se utilizó en diversas ubicaciones en toda la aplicación.
- Los datos de perfilado de bajo nivel obtenidos de tprof son utilizados por un número de vistas. El Navegador de puntos calientes utiliza los datos para visualizar una clasificación de las funciones activas de la aplicación.
- El Visor de fuente de rendimiento y Vista Esquema utilizan los datos para proporcionar los valores de las marcas que se visualizan en líneas o secciones del código fuente.
- El Navegador de comparación de puntos calientes utiliza los datos para obtener los valores de marca para la comparación.
- procstack
En el sistema operativo AIX, el programa de utilidad procstack se utiliza para perfilar los llamadores y los llamados de las funciones.
- Cada segundo, procstack localiza la función en ejecución en ese momento de la aplicación y recorre la vía de la llamada para determinar la cadena de funciones utilizadas para alcanzar la función actualmente en ejecución.
- La vía de llamada se recorre hasta la función main() de la aplicación. El Navegador de invocaciones compone y utiliza esta información de llamada a método.
- El programa de utilidad procstack proporciona cada cadena de llamadas muestreada para la construcción del Navegador de invocaciones.
Dado que la información no se añade a un gráfico de llamada, el Navegador de invocaciones de AIX basado en procstack no sufre posibles vías falsas, comparado con el programa de utilidad oprofile de Linux.
- oprofile
En el sistema operativo Linux, el programa de utilidad oprofile desempeña el mismo papel que tprof en AIX, sirviendo como principal fuente de datos de perfilado de bajo nivel. El programa de utilidad oprofile funciona de la misma manera que tprof en AIX. Sin embargo, la frecuencia de muestreo que se utiliza es cada 150.000 ciclos de procesador. En un procesador de 3,5 GHz, esto equivaldría a cada 43 microsegundos.
De forma parecida a tprof en AIX, los datos de oprofile en Linux son utilizados por el Navegador de puntos calientes, el Visor de fuente de rendimiento, la Vista Esquema y el Navegador de comparación de puntos calientes. En el sistema operativo Linux, el programa de utilidad oprofile también perfila la relación de llamada entre funciones. El Navegador de invocaciones utiliza esta información para visualizar información de vía de llamada de cada función. oprofile realiza el recorrido de vía de llamada más frecuentemente que procstack en AIX, en el orden de cada 43 microsegundos. Sin embargo, debido a la mayor frecuencia de muestreo y también debido a las limitaciones de oprofile, la vía de llamada se recorre de vuelta a una profundidad máxima de 2 llamadores.
Además, oprofile agrega esta información de llamada a un gráfico de llamada en vez de a un árbol de llamadas. Una implicación importante al utilizar un gráfico en lugar de un árbol es que los gráficos podrían mostrar vías o recurrencias que no existen realmente en el programa. Esta característica está presente ya que los gráficos agregan información, lo que provoca la pérdida de información. El fenómeno es similar a cómo, en las estadísticas, se pierde una cierta cantidad de información cuando sólo se conserva un valor promedio de un conjunto de números. La información como la distribución del conjunto de números no se conserva.
En el contexto de información de llamada, donde un nodo (función/método) puede aparecer más de una vez en un árbol de llamada, un gráfico de llamada fusiona esos nodos en un solo nodo, lo que da como resultado la pérdida de una cierta cantidad de información. La implicación de esta característica es que el usuario debe tener en cuenta las posibles vías falsas en un Navegador de invocaciones basado en oprofile de Linux. Tenga en cuenta que el Navegador de invocaciones basado en procstack de AIX no sufre estas posibles vías falsas porque no agrega la información.
- ps
El programa de utilidad ps se utiliza en los sistemas operativos AIX y Linux para proporcionar información sobre los procesos que se ejecutan en la máquina durante el perfilado de la aplicación. Esta información se utiliza para detectar qué procesos pertenecen a la aplicación, en el caso de que la aplicación sea una aplicación multiproceso o se lance desde un script de shell de Unix. Además, también proporciona información básica acerca de cada proceso, como por ejemplo su nombre y argumentos y el proceso padre. El Navegador de puntos calientes utiliza esta información para visualizar información de proceso.
Podemos observar que existen múltiples herramientas de perfilado y la elección del perfilador adecuado depende de las herramientas utilizadas, el nivel de análisis requerido y las características específicas del profiler, es por lo cual, es necesario considerar los requisitos de nuestro proyecto y la información específica que busquemos obtener, para después elegir él perfilador que más se adapte a nuestras necesidades. Por otro lado, otra de las consideraciones principales al elegir un profiler es que se deben considerar las tecnologías de perfilado, de las cuales se hablarán a continuación.
Tecnologías de Perfilado
Hay diversas tecnologías de perfilado ampliamente utilizadas para analizar el rendimiento de una aplicación o sistema en ejecución, permitiendo a los desarrolladores identificar cuellos de botella y otros problemas de rendimiento. Algunas de las tecnologías de perfilado que se utilizan comúnmente son:
- Sampling Profiler
Un perfilador de muestreo es una herramienta que registra la ejecución de una aplicación en intervalos de tiempo predefinidos, tomando muestras de la pila de llamadas. Esto permite obtener una idea de qué partes del código están consumiendo más tiempo de CPU, lo que ayuda a identificar posibles problemas de rendimiento.
- Tracing Profiler
Un perfilador de trazado realiza un seguimiento exhaustivo de todas las llamadas de función y eventos en el código, lo que brinda una visión completa de la ejecución. Aunque puede ser más lento que un perfilador de muestreo, ofrece una mayor precisión para identificar cuellos de botella y otros problemas.
- Instrumentation Profiler
Un perfilador de instrumentación agrega código adicional a la aplicación para registrar datos de rendimiento durante la ejecución. Esto proporciona un nivel de información muy detallado, pero puede afectar significativamente el rendimiento de la aplicación.
- Hardware Profiler
Un perfilador de hardware utiliza dispositivos especializados, como procesadores de rendimiento o analizadores lógicos, para medir el rendimiento de una aplicación. Aunque puede ofrecer una gran cantidad de información detallada, su configuración y costo son más complejos que otras opciones de perfilado.
- Dynamic Tracing
Esta técnica utiliza herramientas como SystemTap o DTrace para monitorear y analizar en tiempo real el comportamiento del sistema. Proporciona información detallada sobre el rendimiento tanto de la aplicación como del sistema subyacente.
- Memory Profiling
Esta técnica se utiliza para monitorear el rendimiento en el uso de memorias, aquí podremos realizar análisis relacionado con el uso de la memoria, la detección de fugas, la liberación de memoria y el análisis de su estructura. El profiling de memoria es útil para solucionar problemas que puedan afectar el rendimiento y que sean relacionados con optimización y desgaste de memoria.
- Input/Output Profiling
El perfilado de entrada y salida se enfoca en analizar el rendimiento de operaciones como la lectura y escritura de archivos, de interacciones de bases de datos o de operaciones en la red, además de ayudarnos a encontrar e identificar llamadas costosas a operaciones de red o encontrar ineficiencias en la manipulación de archivos.
- Net Performance Profiling
Con este método podremos analizar el rendimiento de aquellas aplicaciones o sistemas que estén en constante comunicación con una red, se encuentran relacionadas con los perfiladores de la nube ya que nos permitirán analizar parámetros como la velocidad de transferencia o la latencia de la red, además de identificar problemas de red como el tráfico de esta.
Sistemas distribuidos
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 analicervisen.
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 con base en 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.
Relación con sistemas distribuidos
Debido a su características de análisis de algoritmos, los perfiladores resultan útiles dentro del área de las aplicaciones distribuidas ya que permiten identificar problemas en la implementación mientras éstas se ejecutan. Pudiendo con esto encontrar información sobre el rendimiento de la aplicación; como pueden ser las secciones donde se producen cuellos de botella, o el tiempo que le toma dar solución a peticiones específicas. Algunas de las aplicaciones distribuidas que pueden verse beneficiadas por el uso de estas tecnologías son:
- Sistemas de archivos
Al tratarse de sistemas donde la información es accesada y modificada por distintos agentes, mantener un control sobre los tiempos de acceso y respuesta resulta crucial para garantizar la disponibilidad e integridad de la información. Por lo que el análisis del comportamiento para las operaciones puede verse beneficiado con el uso de perfiladores que identifican las funciones del sistema que presentan problemas de optimización.
- Algoritmos
Gracias a las métricas obtenidas a partir de la recolección de datos de ejecución en una aplicación, el uso de perfiladores es de utilidad cuando se busca probar la eficiencia de algún algoritmo; pues nos permite identificar si las instrucciones utilizadas son las más óptimas para el uso de nuestros recursos según el rendimiento esperado. Dentro del campo de los algoritmos distribuidos, se puede hacer uso de estas herramientas para comprobar que distribución de carga sea equitativa, para evitar con esto problemas de sincronización en los que la ejecución del programa se retrase debido a un nodo donde el tiempo de respuesta sea mayor a los demás.
- Arquitectura
En las arquitecturas distribuidas, donde la cantidad de elementos autónomos interactuando es elevada, se puede hacer uso de estas herramientas para comprobar que la comunicación entre los nodos sea efectiva y no presente problemas, como podría ser la sobrecarga de una red donde la mayoría del tráfico se concentra en pocos nodos, llevando a problemas de latencia cuando otros nodos están disponibles para dar solución a las peticiones.
¿Cómo funciona el rastreo distribuido?
Utilizar el rastreo distribuido es muy útil, pues con todo esto podemos obtener diversos beneficios para los programadores al momento de realizar los análisis correspondientes, entre los beneficios se puede destacar: Reducir el MTTD (Tiempo medio de detección) y el MTTR (Tiempo medio de reparación),comprender las relaciones de los servicios, Medir acciones específicas de los usuarios. Además, identifica los cuellos de botella y los errores del backend que están perjudicando la experiencia del usuario.
Sin embargo, no todo son ventajas con el rastreo distribuido, también con esto llegan algunas dificultades de su implementación: • Instrumentación manual MTTR: Algunas plataformas de rastreo distribuido requieren que se instrumente o modifique manualmente el código para iniciar el rastreo de las solicitudes. • Muestreo basado en el cabezal: Las plataformas de rastreo tradicionales tienden a muestrear aleatoriamente el rastreo justo cuando comienza cada solicitud. • Cobertura solo 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.
Más adelánte se darán mayores detalles de estos aspectos.
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.
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.
Áreas de aplicación de los perfiladores relacionadas con los sistemas distribuidos
En el ámbito de los sistemas distribuidos, resulta fundamental emplear perfiladores, ya que posibilitan el análisis y la optimización del rendimiento de componentes y servicios en entornos donde múltiples recursos operan de manera conjunta y descentralizada. Estos instrumentos valiosos guardan una estrecha relación con aspectos críticos de los sistemas distribuidos. Al llevar a cabo la medición y el análisis detallado de procesos y operaciones distribuidas, los perfiladores desempeñan un papel esencial en la identificación y resolución de problemas de rendimiento, contribuyendo así a un funcionamiento más eficiente y confiable en estos entornos complejos. [8]
- Sincronización: En entornos distribuidos, donde múltiples nodos o componentes interactúan de manera independiente, pero a menudo deben coordinarse, la sincronización es un aspecto crítico para garantizar la coherencia de los datos y la eficiencia del sistema. Los perfiladores son fundamentales para abordar este desafío, ya que permiten un análisis detallado del rendimiento y la sincronización. Al rastrear y registrar meticulosamente el tiempo necesario para completar operaciones específicas en diferentes partes del sistema, los perfiladores pueden revelar problemas de sincronización, como retrasos en la comunicación, bloqueos o cuellos de botella. Esta información es esencial para los desarrolladores y administradores de sistemas, ya que les permite tomar medidas precisas para optimizar la sincronización y mejorar la eficiencia operativa en el entorno distribuido.
- Algoritmos distribuidos: Si bien los perfiladores no están directamente involucrados en el diseño de algoritmos distribuidos, desempeñan un papel crítico en la evaluación y optimización de su rendimiento en entornos del mundo real. Estas herramientas permiten medir el tiempo que lleva ejecutar diferentes partes de un algoritmo distribuido, lo que facilita la identificación de cuellos de botella, ineficiencias o retrasos en su implementación. Esta información es esencial para ajustar y mejorar los algoritmos distribuidos, asegurando que funcionen de manera eficiente en sistemas distribuidos reales. Los perfiladores ayudan a comprender cómo se ejecutan los algoritmos en condiciones variables y cómo interactúan con otros componentes distribuidos.
- Arquitecturas distribuidas: Las arquitecturas distribuidas se caracterizan por la colaboración de múltiples componentes o servicios que operan de manera autónoma, pero interactúan para brindar una funcionalidad completa. Los perfiladores desempeñan un papel crítico al permitir una evaluación exhaustiva de cómo estos componentes se comunican y se relacionan. Mediante la medición y el análisis del rendimiento de cada componente de manera individual, los perfiladores ayudan a identificar ineficiencias y desequilibrios en la distribución de la carga de trabajo. Esto lleva a decisiones informadas para optimizar la arquitectura distribuida. Los resultados obtenidos a través de los perfiladores pueden implicar la redistribución de tareas entre los nodos, la mejora de la escalabilidad, la garantía de la alta disponibilidad o la adaptación a la demanda variable, lo que en última instancia contribuye a una arquitectura más eficiente y confiable.
- Sistemas de archivos distribuidos: En los sistemas de archivos distribuidos, donde los datos se almacenan y acceden desde múltiples nodos, los perfiladores desempeñan un papel crucial. Estas herramientas permiten medir de manera precisa el rendimiento de las operaciones de lectura y escritura en un sistema de almacenamiento distribuido. Los datos recopilados por los perfiladores revelan el tiempo necesario para acceder a los datos distribuidos, lo que a su vez ayuda a identificar cuellos de botella en el acceso a archivos, retrasos en la recuperación de información y problemas de latencia. Con esta información detallada, los administradores pueden tomar decisiones informadas para mejorar la velocidad y la disponibilidad de los datos, lo que es esencial en aplicaciones que requieren un acceso rápido y confiable a la información.
- Sistemas de extremo a extremo (P2P): En sistemas extremo a extremo, como los sistemas P2P, donde la comunicación directa entre pares es fundamental, los perfiladores desempeñan un papel relevante. Estas herramientas permiten evaluar el rendimiento de la comunicación entre pares y la eficiencia de la transferencia de datos. Registran datos como el tiempo de respuesta, la latencia y el ancho de banda utilizado durante las comunicaciones entre pares. Esto facilita la identificación de problemas de rendimiento en la red o ineficiencias en la comunicación, lo que es esencial para mejorar la calidad y la eficiencia de la comunicación extremo a extremo en sistemas P2P.
- Transacciones distribuidas: Las transacciones distribuidas involucran operaciones complejas que abarcan múltiples nodos o componentes. Los perfiladores son esenciales en este contexto para medir y analizar el rendimiento de las transacciones. Registran detalladamente el tiempo necesario para completar cada paso de una transacción, lo que permite identificar posibles cuellos de botella, ineficiencias o retrasos en el proceso. Además, ayudan a identificar problemas de integridad de datos y consistencia en entornos de transacción distribuida. Esta información es crucial en aplicaciones que requieren una alta fiabilidad y coherencia de datos, como sistemas financieros o de comercio electrónico.
Los perfiladores se convierten en aliados esenciales para aquellos que trabajan en el diseño, desarrollo y operación de sistemas distribuidos. Su papel en la identificación de cuellos de botella, la optimización de algoritmos, la evaluación de arquitecturas y la mejora del rendimiento global es insustituible. Por lo tanto, es fundamental reconocer su importancia y aprovechar al máximo su potencial para mantener y hacer evolucionar con éxito los sistemas distribuidos en un entorno tecnológico en constante cambio.[9]
Resume
Actualmente para poder realizar perfilados de programas existe una gran variedad herramientas y tecnologías que se han desarrollado a lo largo de los años por medio de las diversas metodologías que llegan a existir; sin embargo, a pesar de cumplir con su propósito estas presentan ventajas y desventajas que se deben considerar al momento de realizar el perfilado de nuestros programas. Por otra parte, el perfilado es de gran importancia para la programación incluyendo los sistemas distribuidos, ya que para estos resulta muy importante poder conocer que, a pesar de que los tiempos de comunicación entre dispositivos sea largo, siempre resulten más rápidos al momento de ejecutar acciones, ya que esto es parte fundamental del concepto de un sistema distribuido y sin un perfilado correcto podría cometerse el error de programar un conjunto de algortimos que tengan problemas al momento de compartir recursos y no elimine los cuellos de botella al momento de la ejecución del programa, o incluso que los programas que componen al sistema no logre n una descentralización de los recursos para el almacenamiento y procesamiento de información o datos es decir, que no cumpla con ser un sistema distribuido.
Véase también
- Pruebas de rendimiento del software
- Análisis dinámico de software
- Pruebas de rendimiento a componentes de software utilizando programación orientada a aspectos
Referencias
- ↑ Profiling. Estratecgias. [1]
- ↑ Funcionalidad implementada por profilers. [2]
- ↑ Computerworld 19 de septiembre de 1977
- ↑ 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
- ↑ 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
- ↑ 20 Years of PLDI (1979–1999): A Selection, Kathryn S. McKinley, Editor
- ↑ Emilio Coppa; Camil Demetrescu; Irene Finocchi (17 de julio de 2014). «Input-Sensitive Profiling». pp. 1185 - 1205. doi:10.1109/TSE.2014.2339825.
- ↑ Silberschatz, A. (1979-11). «Communication and Synchronization in Distributed Systems». IEEE Transactions on Software Engineering. SE-5 (6): 542-546. ISSN 0098-5589. doi:10.1109/tse.1979.230190. Consultado el 30 de noviembre de 2023.
- ↑ «IEEE Transactions on Parallel and Distributed Systems». IEEE Transactions on Parallel and Distributed Systems 15 (5): 01-01. 2004-05. ISSN 1045-9219. doi:10.1109/tpds.2004.1278095. Consultado el 30 de noviembre de 2023.
[1] Yegulalp. S. (23 mayo, 2022).9 fine libraries for profiling python code. Recuperado el 25 de marzo de 2023 de https://www.infoworld.com/article/3600993/9 -fine - libraries -for - profiling -python -code.html
[2] Baeldung. (17 marzo, 2023). A guide to java profilers. Recuperado el 23 de marzo de 2023 de https://www.baeldung.com/java -profilers
[3] ChatGPT. (s.f.). ChatGPT. Recuperado de https://chat.openai.com/chat (enlace roto disponible en Internet Archive; véase el historial, la primera versión y la última).
[4] A. (2021b, diciembre 26). ¿Qué es el rastreo distribuido? Definición de SearchITOperations (Actualizado 2023). Krypton Solid. https://kryptonsolid.com/que -es -el -rastreo -distribuido -definicion -de - searchitoperations/
[5] Rastreo distribuido, OpenTracing y Elastic APM. (2019, 3 junio). Elastic Blog. https://www.elastic.co/es/blog/distributed -tracing -opentracing -and - elastic -apm
[6] Why is Distributed Tracing in Microservices needed? (2022b, marzo 18). SigNoz. https://signoz.io/blog/distributed -tracing -in -microservices/
[7] Hager, G., & Wellein, G. (2010). Introduction to High Performance Computing for Scientists and Engineers. CRC Press.
[8] Herramientas de perfilado de bajo nivel. (03 junio, 2021). IBM Knowledge Center. https://www.ibm.com/docs/es/rdfa-and-l/9.1.0?topic=reference-low-level-data-profiling-tools
Enlaces externos
- Prioritizing Software Inspection Results using Static Profiling, Cathal Boogerd and Leon Moonen. 2006. Delft University of Technology.
- Profiling Deployed Software: Assessing Strategies and Testing Opportunities Archivado el 23 de agosto de 2011 en Wayback Machine., Sebastian Elbaum, Member, IEEE, and Madeline Diep. Agosto 2005. IEEE TRANSACTIONS ON SOFTWARE ENGINEERING, VOL. 31, NO. 8.
- Software Performance Profiling, Chandra Krintz, Tim Sherwood, Tevfik Bultan. 6-5-2008. UCSB.
- FUNCIONALIDAD IMPLEMENTADA POR PROFILERS EN PYTHON
- Realizando profiling de aplicaciones Java en JBoss
- Descripción de cursos: Profiling y Tunning en Java, IBM.
- Distributed Tracing Overview
Datos: Q1138496