¿Cómo puedo perfilar el código C ++ que se ejecuta en Linux?
1816
Tengo una aplicación C ++, que se ejecuta en Linux, que estoy en proceso de optimización. ¿Cómo puedo determinar qué áreas de mi código se ejecutan lentamente?
Si proporciona más datos sobre su pila de desarrollo, podría obtener mejores respuestas. Hay perfiladores de Intel y Sun pero debes usar sus compiladores. ¿Es esa una opción?
La mayoría de las respuestas son codeperfiladores. Sin embargo, la inversión prioritaria, el alias de caché, la contención de recursos, etc., pueden ser factores para la optimización y el rendimiento. Creo que la gente lee información en mi código lento . Las preguntas frecuentes hacen referencia a este hilo.
Solía usar pstack al azar, la mayoría de las veces imprime la pila más típica donde el programa está la mayor parte del tiempo, por lo tanto, apunta al cuello de botella.
José Manuel Gómez Álvarez
Respuestas:
1406
Si su objetivo es usar un generador de perfiles, use uno de los sugeridos.
Sin embargo, si tiene prisa y puede interrumpir manualmente su programa bajo el depurador mientras es subjetivamente lento, hay una manera simple de encontrar problemas de rendimiento.
Simplemente deténgalo varias veces, y cada vez mire la pila de llamadas. Si hay algún código que está desperdiciando un porcentaje del tiempo, 20% o 50% o lo que sea, esa es la probabilidad de que lo atrape en el acto en cada muestra. Entonces, ese es aproximadamente el porcentaje de muestras en el que lo verá. No se requieren conjeturas educadas. Si tiene una idea de cuál es el problema, esto lo probará o lo desaprobará.
Puede tener múltiples problemas de rendimiento de diferentes tamaños. Si limpia alguno de ellos, los restantes tomarán un porcentaje mayor y serán más fáciles de detectar en los pases posteriores. Este efecto de aumento , cuando se combina con múltiples problemas, puede conducir a factores de aceleración verdaderamente masivos.
Advertencia : los programadores tienden a ser escépticos de esta técnica a menos que la hayan usado ellos mismos. Dirán que los perfiladores le brindan esta información, pero eso solo es cierto si toman muestras de toda la pila de llamadas y luego le permiten examinar un conjunto aleatorio de muestras. (Los resúmenes son donde se pierde la información). Los gráficos de llamadas no le brindan la misma información, porque
No resumen en el nivel de instrucción, y
Dan resúmenes confusos en presencia de recursividad.
También dirán que solo funciona en programas de juguetes, cuando en realidad funciona en cualquier programa, y parece funcionar mejor en programas más grandes, porque tienden a tener más problemas para encontrar. Dirán que a veces encuentra cosas que no son problemas, pero eso solo es cierto si ves algo una vez . Si ve un problema en más de una muestra, es real.
PD Esto también se puede hacer en programas de subprocesos múltiples si hay una manera de recopilar muestras de la pila de llamadas del grupo de subprocesos en un momento dado, como en Java.
PPS Como una generalidad aproximada, mientras más capas de abstracción tenga en su software, es más probable que descubra que esa es la causa de los problemas de rendimiento (y la oportunidad de acelerar).
Adicional : Puede que no sea obvio, pero la técnica de muestreo de pila funciona igualmente bien en presencia de recursividad. La razón es que el tiempo que se ahorraría al eliminar una instrucción se aproxima por la fracción de muestras que la contienen, independientemente de la cantidad de veces que pueda ocurrir dentro de una muestra.
Otra objeción que escucho a menudo es: " Se detendrá en algún lugar al azar, y perderá el verdadero problema ". Esto viene de tener un concepto previo de cuál es el verdadero problema. Una propiedad clave de los problemas de rendimiento es que desafían las expectativas. El muestreo te dice que algo es un problema, y tu primera reacción es de incredulidad. Eso es natural, pero puede estar seguro de que si encuentra un problema, es real y viceversa.
Agregado : Permítanme hacer una explicación bayesiana de cómo funciona. Supongamos que hay alguna instrucción I(llamada o no) que está en la pila de llamadas una fracción fdel tiempo (y por lo tanto cuesta tanto). Por simplicidad, supongamos que no sabemos qué fes, pero supongamos que es 0.1, 0.2, 0.3, ... 0.9, 1.0, y la probabilidad previa de cada una de estas posibilidades es 0.1, por lo que todos estos costos son igualmente probables a priori.
Luego, supongamos que tomamos solo 2 muestras de pila y vemos instrucciones Ien ambas muestras, observación designada o=2/2. Esto nos da nuevas estimaciones de la frecuencia fde Iacuerdo con esto:
Prior
P(f=x) x P(o=2/2|f=x) P(o=2/2&&f=x) P(o=2/2&&f >= x) P(f >= x | o=2/2)0.1110.10.10.259740260.10.90.810.0810.1810.470129870.10.80.640.0640.2450.6363636360.10.70.490.0490.2940.7636363640.10.60.360.0360.330.8571428570.10.50.250.0250.3550.9220779220.10.40.160.0160.3710.9636363640.10.30.090.0090.380.9870129870.10.20.040.0040.3840.9974025970.10.10.010.0010.3851
P(o=2/2)0.385
La última columna dice que, por ejemplo, la probabilidad de que f> = 0.5 sea del 92%, por encima del supuesto anterior del 60%.
Supongamos que los supuestos anteriores son diferentes. Supongamos que suponemos P(f=0.1).991 (casi seguro), y todas las demás posibilidades son casi imposibles (0.001). En otras palabras, nuestra certeza previa es que Ies barata. Entonces obtenemos:
Prior
P(f=x) x P(o=2/2|f=x) P(o=2/2&& f=x) P(o=2/2&&f >= x) P(f >= x | o=2/2)0.001110.0010.0010.0727272730.0010.90.810.000810.001810.1316363640.0010.80.640.000640.002450.1781818180.0010.70.490.000490.002940.2138181820.0010.60.360.000360.00330.240.0010.50.250.000250.003550.2581818180.0010.40.160.000160.003710.2698181820.0010.30.090.000090.00380.2763636360.0010.20.040.000040.003840.2792727270.9910.10.010.009910.013751
P(o=2/2)0.01375
Ahora dice que P(f >= 0.5)es 26%, por encima del supuesto anterior de 0.6%. Entonces Bayes nos permite actualizar nuestra estimación del costo probable de I. Si la cantidad de datos es pequeña, no nos dice con precisión cuál es el costo, solo que es lo suficientemente grande como para que valga la pena arreglarlo.
Otra forma de verlo se llama la Regla de Sucesión . Si lanzas una moneda 2 veces, y sale cara en ambas ocasiones, ¿qué te dice eso sobre el probable peso de la moneda? La forma respetada de responder es decir que es una distribución Beta, con un valor promedio (number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%.
(La clave es que vemos Imás de una vez. Si solo lo vemos una vez, eso no nos dice mucho, excepto que f> 0.)
Entonces, incluso un número muy pequeño de muestras puede decirnos mucho sobre el costo de las instrucciones que ve. (Y va a ver con una frecuencia de, en promedio, proporcional a su costo. Si nse toman muestras, y fes el costo, entonces Ivan a aparecer en nf+/-sqrt(nf(1-f))las muestras. Ejemplo, n=10, f=0.3, es decir 3+/-1.4muestras.)
Agregado : para dar una idea intuitiva de la diferencia entre la medición y el muestreo aleatorio de la pila:
ahora hay perfiladores que muestrean la pila, incluso en el tiempo del reloj de pared, pero lo que sale son las mediciones (o ruta caliente, o punto caliente, desde el cual un "cuello de botella" puede esconderse fácilmente). Lo que no le muestran (y podrían fácilmente) son las muestras en sí mismas. Y si su objetivo es encontrar el cuello de botella, el número de ellos que necesita ver es, en promedio , 2 dividido por la fracción de tiempo que lleva. Entonces, si toma el 30% del tiempo, 2 / .3 = 6.7 muestras, en promedio, lo mostrarán, y la probabilidad de que 20 muestras lo muestren es 99.2%.
Aquí hay una ilustración poco convencional de la diferencia entre examinar mediciones y examinar muestras de pila. El cuello de botella podría ser una gran gota como esta, o numerosas pequeñas, no hay diferencia.
La medida es horizontal; le dice qué fracción de tiempo toman rutinas específicas. El muestreo es vertical. Si hay alguna forma de evitar lo que está haciendo todo el programa en ese momento, y si lo ve en una segunda muestra , ha encontrado el cuello de botella. Eso es lo que marca la diferencia: ver la razón completa del tiempo que se pasa, no solo cuánto.
Esto es básicamente el perfilador de muestreo de un hombre pobre, lo cual es genial, pero corre el riesgo de un tamaño de muestra demasiado pequeño que posiblemente le dará resultados completamente espurios.
Crashworks
100
@Crash: No voy a debatir la parte del "pobre hombre" :-) Es cierto que la precisión de la medición estadística requiere muchas muestras, pero hay dos objetivos en conflicto: la medición y la ubicación del problema. Me estoy centrando en lo último, para lo cual necesitas precisión de ubicación, no precisión de medida. Entonces, por ejemplo, puede haber, mid-stack, una sola función llamada A (); eso representa el 50% del tiempo, pero puede estar en otra función grande B, junto con muchas otras llamadas a A () que no son costosas. Los resúmenes precisos de los tiempos de función pueden ser una pista, pero cualquier otra muestra de la pila determinará el problema.
Mike Dunlavey
41
... el mundo parece pensar que un gráfico de llamadas, anotado con conteos de llamadas y / o tiempo promedio, es lo suficientemente bueno. No lo es. Y la parte triste es que, para aquellos que prueban la pila de llamadas, la información más útil está justo en frente de ellos, pero la descartan, en interés de las "estadísticas".
Mike Dunlavey
30
No quiero estar en desacuerdo con tu técnica. Claramente, confío bastante en los perfiladores de muestreo de caminata de pila. Solo estoy señalando que hay algunas herramientas que lo hacen de manera automatizada ahora, lo cual es importante cuando pasas el punto de obtener una función del 25% al 15% y necesitas reducirla del 1.2% al 0.6%
Crashworks
13
-1: Buena idea, pero si te pagan por trabajar incluso en un entorno moderadamente orientado al rendimiento, esto es una pérdida de tiempo para todos. Use un perfilador real para que no tengamos que ir detrás de usted y solucionar los problemas reales.
Generará un archivo llamado callgrind.out.x. Luego puede usar la kcachegrindherramienta para leer este archivo. Le dará un análisis gráfico de las cosas con resultados como qué líneas cuestan cuánto.
valgrind es genial, pero ten en cuenta que hará que tu programa sea muy lento
neves
30
Echa un vistazo también a Gprof2Dot para obtener una forma alternativa increíble de visualizar la salida. ./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
Sebastian
2
@neves Sí Valgrind simplemente no es muy útil en términos de velocidad para perfilar aplicaciones "gstreamer" y "opencv" en tiempo real.
Estoy de acuerdo en que gprof es el estándar actual. Sin embargo, solo una nota, Valgrind se usa para perfilar fugas de memoria y otros aspectos relacionados con la memoria de sus programas, no para la optimización de la velocidad.
Bill the Lizard
68
Bill, en la suite vaglrind puedes encontrar callgrind y macizo. Ambas son bastante útiles para las aplicaciones de perfil
gprof -pg es solo una aproximación del perfil de pila de llamadas. Inserta llamadas de mcount para rastrear qué funciones están llamando a qué otras funciones. Utiliza muestreo estándar basado en el tiempo para, uh, tiempo. Luego distribuye los tiempos muestreados en una función foo () de regreso a las personas que llaman de foo (), en proporción al número de llamadas. Por lo tanto, no distingue entre llamadas de diferentes costos.
Krazy Glew
1
Con clang / clang ++, uno podría considerar usar el perfilador de CPU de gperftools . Advertencia: no lo he hecho yo mismo.
einpoklum
257
Los nuevos núcleos (por ejemplo, los últimos núcleos de Ubuntu) vienen con las nuevas herramientas 'perf' ( apt-get install linux-tools) AKA perf_events .
Lo importante es que estas herramientas pueden ser la creación de perfiles del sistema y no solo la creación de perfiles de procesos: pueden mostrar la interacción entre subprocesos, procesos y el núcleo y le permiten comprender la programación y las dependencias de E / S entre procesos.
Gran herramienta! ¿Hay alguna forma de obtener una vista típica de "mariposa" que comience desde el estilo "main-> func1-> fun2"? Parece que no puedo entender eso ... perf reportparece darme los nombres de las funciones con los padres de la llamada ... (por lo que es una especie de vista de mariposa invertida)
kizzx2
Will, puede mostrar el diagrama de tiempo de la actividad del hilo; con información de número de CPU agregada? Quiero ver cuándo y qué hilo se estaba ejecutando en cada CPU.
osgx
2
@ kizzx2: puede usar gprof2doty perf script. Muy buena herramienta!
Otra introducción bueno perfexiste en archive.li/9r927#selection-767.126-767.271 (¿Por qué los dioses por lo que decidió eliminar esa página de la base de conocimientos por lo que es más allá de mí ....)
ragerdl
75
Usaría Valgrind y Callgrind como base para mi conjunto de herramientas de creación de perfiles. Lo importante es saber que Valgrind es básicamente una máquina virtual:
(wikipedia) Valgrind es, en esencia, una máquina virtual que utiliza técnicas de compilación justo a tiempo (JIT), incluida la recompilación dinámica. Nada del programa original se ejecuta directamente en el procesador host. En cambio, Valgrind primero traduce el programa en una forma temporal y más simple llamada Representación Intermedia (IR), que es una forma basada en SSA y neutral del procesador. Después de la conversión, una herramienta (ver más abajo) es libre de hacer cualquier transformación que desee en el IR, antes de que Valgrind traduzca el IR nuevamente en el código de la máquina y permita que el procesador host lo ejecute.
Callgrind es un generador de perfiles sobre eso. El principal beneficio es que no tiene que ejecutar su aplicación durante horas para obtener resultados confiables. Incluso una segunda carrera es suficiente para obtener resultados sólidos y confiables, ya que Callgrind es un generador de perfiles sin sondeo .
Otra herramienta construida sobre Valgrind es Massif. Lo uso para perfilar el uso de memoria del montón. Funciona muy bien Lo que hace es que le da instantáneas del uso de la memoria: información detallada QUÉ contiene QUÉ porcentaje de memoria, y QUIÉN lo había puesto allí. Dicha información está disponible en diferentes puntos de tiempo de ejecución de la aplicación.
La respuesta para ejecutar valgrind --tool=callgrindno está completa sin algunas opciones. Por lo general, no queremos perfilar 10 minutos de tiempo de inicio lento en Valgrind y queremos perfilar nuestro programa cuando está realizando alguna tarea.
Entonces esto es lo que recomiendo. Ejecute el programa primero:
Ahora, cuando funciona y queremos comenzar a generar perfiles, debemos ejecutar en otra ventana:
callgrind_control -i on
Esto activa la creación de perfiles. Para apagarlo y detener toda la tarea, podemos usar:
callgrind_control -k
Ahora tenemos algunos archivos llamados callgrind.out. * En el directorio actual. Para ver resultados de perfiles, use:
kcachegrind callgrind.out.*
Recomiendo en la siguiente ventana hacer clic en el encabezado de la columna "Self", de lo contrario, muestra que "main ()" es la tarea que requiere más tiempo. "Self" muestra cuánto tiempo llevó cada función en sí, no junto con las personas dependientes.
Ahora, por alguna razón, los archivos callgrind.out. * Siempre estaban vacíos. Ejecutar callgrind_control -d fue útil para forzar el volcado de datos al disco.
Tõnu Samuel
3
Hipocresía. Mis contextos habituales son algo como MySQL o PHP completo o algo similar. A menudo, incluso no sé lo que quiero separar al principio.
Tõnu Samuel
2
O en mi caso, mi programa realmente carga un montón de datos en un caché LRU, y no quiero hacer un perfil de eso. Por lo tanto, forzo la carga de un subconjunto de la memoria caché al inicio y perfilo el código usando solo esos datos (permitiendo que la CPU OS + administre el uso de memoria dentro de mi memoria caché). Funciona, pero cargar ese caché es lento e intensivo de la CPU en el código que estoy tratando de perfilar en un contexto diferente, por lo que callgrind produce resultados muy contaminados.
Código Abominator
2
también hay CALLGRIND_TOGGLE_COLLECTque habilitar / deshabilitar la recopilación mediante programación; ver stackoverflow.com/a/13700817/288875
He estado usando Gprof los últimos días y ya he encontrado tres limitaciones significativas, una de las cuales no he visto documentada en ningún otro lugar (todavía):
No funciona correctamente en código multiproceso, a menos que use una solución alternativa
El gráfico de llamadas se confunde con los punteros de función. Ejemplo: Tengo una función llamada multithread()que me permite realizar múltiples subprocesos de una función específica sobre una matriz específica (ambas pasadas como argumentos). Sin embargo, Gprof considera que todas las llamadas multithread()son equivalentes para calcular el tiempo que se pasa en los niños. Dado que algunas funciones que paso multithread()tardan mucho más que otras, mis gráficos de llamadas son en su mayoría inútiles. (Para aquellos que se preguntan si el problema es el enhebrado aquí: no, multithread()opcionalmente, y en este caso, ejecuté todo secuencialmente solo en el hilo de llamada).
Aquí dice que "... las cifras del número de llamadas se obtienen contando, no muestreando. Son completamente precisas ...". Sin embargo, encuentro que mi gráfico de llamadas me da 5345859132 + 784984078 como estadísticas de llamadas a mi función más llamada, donde se supone que el primer número son llamadas directas y las segundas llamadas recursivas (que son todas de sí mismo). Como esto implicaba que tenía un error, puse contadores largos (64 bits) en el código y volví a hacer lo mismo. Mis recuentos: 5345859132 directo y 78094395406 llamadas autorrecurrentes. Hay muchos dígitos allí, así que señalaré que las llamadas recursivas que mido son 78 mil millones, frente a 784 millones de Gprof: un factor de 100 diferentes. Ambas ejecuciones fueron de un solo subproceso y código no optimizado, uno compilado -gy el otro -pg.
Este fue GNU Gprof (GNU Binutils para Debian) 2.18.0.20080103 ejecutándose bajo Debian Lenny de 64 bits, si eso ayuda a alguien.
Sí, hace un muestreo, pero no para cifras de número de llamadas. Curiosamente, seguir su enlace finalmente me llevó a una versión actualizada de la página del manual que enlacé en mi publicación, nueva URL: sourceware.org/binutils/docs/gprof/… Esto repite la cita en la parte (iii) de mi respuesta, pero también dice "En aplicaciones de subprocesos múltiples o aplicaciones de subprocesos simples que se vinculan con bibliotecas de subprocesos múltiples, los recuentos solo son deterministas si la función de recuento es segura para subprocesos. (Nota: tenga en cuenta que la función de recuento de mcount en glibc no es subproceso -seguro)."
Rob_before_edits
No me queda claro si esto explica mi resultado en (iii). Mi código estaba vinculado -lpthread -lm y declaró tanto una variable estática "pthread_t * thr" como una "pthread_mutex_t nextLock = PTHREAD_MUTEX_INITIALIZER" incluso cuando se ejecutaba un solo subproceso. Normalmente presumiría que "vincular con bibliotecas multiproceso" significa usar esas bibliotecas, y en mayor medida que esto, ¡pero podría estar equivocado!
Rob_before_edits
23
Use Valgrind, callgrind y kcachegrind:
valgrind --tool=callgrind ./(Your binary)
genera callgrind.out.x. Léalo usando kcachegrind.
Use gprof (agregar -pg):
cc -o myprog myprog.c utils.c -g -pg
(no tan bueno para subprocesos múltiples, punteros de función)
Utiliza google-perftools:
Utiliza el muestreo de tiempo, se revelan los cuellos de botella de E / S y CPU.
Intel VTune es el mejor (gratis para fines educativos).
Otros: AMD Codeanalyst (desde que se reemplazó con AMD CodeXL), OProfile, herramientas 'perf' (apt-get install linux-tools)
En esta respuesta, usaré varias herramientas diferentes para analizar algunos programas de prueba muy simples, a fin de comparar concretamente cómo funcionan esas herramientas.
El siguiente programa de prueba es muy simple y hace lo siguiente:
mainllamadas fasty maybe_slow3 veces, una de las maybe_slowllamadas es lenta
La llamada lenta de maybe_slowes 10 veces más larga y domina el tiempo de ejecución si consideramos las llamadas a la función secundaria common. Idealmente, la herramienta de creación de perfiles podrá indicarnos la llamada lenta específica.
ambos fast y maybe_slowcall common, que representa la mayor parte de la ejecución del programa
La interfaz del programa es:
./main.out [n [seed]]
y el programa hace O(n^2) bucles en total. seedes solo para obtener una salida diferente sin afectar el tiempo de ejecución.
C Principal
#include<inttypes.h>#include<stdio.h>#include<stdlib.h>uint64_t __attribute__ ((noinline)) common(uint64_t n,uint64_t seed){for(uint64_t i =0; i < n;++i){
seed =(seed * seed)-(3* seed)+1;}return seed;}uint64_t __attribute__ ((noinline)) fast(uint64_t n,uint64_t seed){uint64_t max =(n /10)+1;for(uint64_t i =0; i < max;++i){
seed = common(n,(seed * seed)-(3* seed)+1);}return seed;}uint64_t __attribute__ ((noinline)) maybe_slow(uint64_t n,uint64_t seed,int is_slow){uint64_t max = n;if(is_slow){
max *=10;}for(uint64_t i =0; i < max;++i){
seed = common(n,(seed * seed)-(3* seed)+1);}return seed;}int main(int argc,char**argv){uint64_t n, seed;if(argc >1){
n = strtoll(argv[1], NULL,0);}else{
n =1;}if(argc >2){
seed = strtoll(argv[2], NULL,0);}else{
seed =0;}
seed += maybe_slow(n, seed,0);
seed += fast(n, seed);
seed += maybe_slow(n, seed,1);
seed += fast(n, seed);
seed += maybe_slow(n, seed,0);
seed += fast(n, seed);
printf("%" PRIX64 "\n", seed);return EXIT_SUCCESS;}
gprof
gprof requiere recompilar el software con instrumentación, y también utiliza un enfoque de muestreo junto con esa instrumentación. Por lo tanto, logra un equilibrio entre la precisión (el muestreo no siempre es completamente exacto y puede omitir funciones) y la ralentización de la ejecución (la instrumentación y el muestreo son técnicas relativamente rápidas que no ralentizan mucho la ejecución).
gprof está integrado en GCC / binutils, por lo que todo lo que tenemos que hacer es compilar con la -pgopción para habilitar gprof. Luego ejecutamos el programa normalmente con un parámetro CLI de tamaño que produce una ejecución de duración razonable de unos segundos ( 10000):
Por razones educativas, también haremos una ejecución sin optimizaciones habilitadas. Tenga en cuenta que esto es inútil en la práctica, ya que normalmente solo le importa optimizar el rendimiento del programa optimizado:
Primero, timenos dice que el tiempo de ejecución con y sin -pgfue el mismo, lo cual es genial: ¡no hay desaceleración! Sin embargo, he visto cuentas de ralentizaciones 2x - 3x en software complejo, por ejemplo, como se muestra en este ticket .
Debido a que compilamos -pg, ejecutar el programa produce un archivo gmon.outque contiene los datos de creación de perfiles.
Aquí, la gprofherramienta lee la gmon.outinformación de rastreo y genera un informe legible por humanos main.gprof, que gprof2dotluego se lee para generar un gráfico.
La -O0salida es bastante autoexplicativa. Por ejemplo, muestra que las 3 maybe_slowllamadas y sus llamadas secundarias ocupan el 97.56% del tiempo de ejecución total, aunque la ejecución de maybe_slowsí mismo sin hijos representa el 0.00% del tiempo de ejecución total, es decir, casi todo el tiempo dedicado a esa función se gastó en niño llama.
TODO: ¿por qué mainfalta en la -O3salida, aunque puedo verlo en un bten GDB? Falta la función de la salida de GProf . Creo que es porque gprof también está basado en muestreo además de su instrumentación compilada, y -O3maines demasiado rápido y no tiene muestras.
Elijo la salida SVG en lugar de PNG porque la SVG se puede buscar con Ctrl + F y el tamaño del archivo puede ser aproximadamente 10 veces más pequeño. Además, el ancho y la altura de la imagen generada pueden ser enormes con decenas de miles de píxeles para software complejo, y GNOME eog3.28.1 produce errores en ese caso para PNG, mientras que mi navegador abre automáticamente los SVG. Sin embargo, gimp 2.8 funcionó bien, ver también:
pero incluso así, arrastrará la imagen mucho para encontrar lo que desea, vea, por ejemplo, esta imagen de un ejemplo de software "real" tomado de este ticket :
¿Puedes encontrar la pila de llamadas más crítica fácilmente con todas esas pequeñas líneas de espagueti sin clasificar que se cruzan entre sí? Puede haber mejores dotopciones, estoy seguro, pero no quiero ir allí ahora. Lo que realmente necesitamos es un visor dedicado adecuado para él, pero aún no he encontrado uno:
Sin embargo, puede usar el mapa de colores para mitigar un poco esos problemas. Por ejemplo, en la gran imagen anterior, finalmente logré encontrar el camino crítico a la izquierda cuando hice la brillante deducción de que el verde viene después del rojo, seguido finalmente por un azul más y más oscuro.
Alternativamente, también podemos observar la salida de texto de la gprofherramienta binutils incorporada que guardamos previamente en:
cat main.gprof
Por defecto, esto produce una salida extremadamente detallada que explica lo que significan los datos de salida. Como no puedo explicarlo mejor que eso, te dejaré leerlo tú mismo.
Una vez que haya entendido el formato de salida de datos, puede reducir la verbosidad para mostrar solo los datos sin el tutorial con la -bopción:
gprof -b main.out
En nuestro ejemplo, los resultados fueron para -O0:
Flat profile:Each sample counts as 0.01 seconds.% cumulative self self total
time seconds seconds calls s/call s/call name
100.353.673.671230030.000.00 common
0.003.670.0030.000.03 fast
0.003.670.0030.001.19 maybe_slow
Call graph
granularity: each sample hit covers 2 byte(s)for0.27% of 3.67 seconds
index % time self children called name
0.090.003003/123003 fast [4]3.580.00120000/123003 maybe_slow [3][1]100.03.670.00123003 common [1]-----------------------------------------------<spontaneous>[2]100.00.003.67 main [2]0.003.583/3 maybe_slow [3]0.000.093/3 fast [4]-----------------------------------------------0.003.583/3 main [2][3]97.60.003.583 maybe_slow [3]3.580.00120000/123003 common [1]-----------------------------------------------0.000.093/3 main [2][4]2.40.000.093 fast [4]0.090.003003/123003 common [1]-----------------------------------------------Index by function name
[1] common [4] fast [3] maybe_slow
y para -O3:
Flat profile:Each sample counts as 0.01 seconds.% cumulative self self total
time seconds seconds calls us/call us/call name
100.521.841.8412300314.9614.96 common
Call graph
granularity: each sample hit covers 2 byte(s)for0.54% of 1.84 seconds
index % time self children called name
0.040.003003/123003 fast [3]1.790.00120000/123003 maybe_slow [2][1]100.01.840.00123003 common [1]-----------------------------------------------<spontaneous>[2]97.60.001.79 maybe_slow [2]1.790.00120000/123003 common [1]-----------------------------------------------<spontaneous>[3]2.40.000.04 fast [3]0.040.003003/123003 common [1]-----------------------------------------------Index by function name
[1] common
Como un resumen muy rápido para cada sección, por ejemplo:
0.003.583/3 main [2][3]97.60.003.583 maybe_slow [3]3.580.00120000/123003 common [1]
se centra en la función que queda sangrada ( maybe_flow). [3]es la identificación de esa función. Encima de la función, están sus llamadores, y debajo de ella los callees.
Para -O3, vea aquí como en la salida gráfica que maybe_slowy fastno tiene un padre conocido, que es lo que la documentación dice que <spontaneous>significa.
valgrind ejecuta el programa a través de la máquina virtual valgrind. Esto hace que la creación de perfiles sea muy precisa, pero también produce una gran desaceleración del programa. También he mencionado kcachegrind anteriormente en: Herramientas para obtener un gráfico de llamada de función pictórica de código
callgrind es la herramienta de valgrind para perfilar código y kcachegrind es un programa de KDE que puede visualizar la salida de cachegrind.
Primero tenemos que eliminar la -pgbandera para volver a la compilación normal, de lo contrario, la ejecución realmente falla Profiling timer expired, y sí, esto es tan común que lo hice y había una pregunta de desbordamiento de pila.
Habilito --dump-instr=yes --collect-jumps=yesporque esto también volca la información que nos permite ver un desglose del rendimiento por línea de ensamblaje, a un costo general agregado relativamente pequeño.
De buenas a primeras, timenos dice que el programa tardó 29,5 segundos en ejecutarse, por lo que tuvimos una desaceleración de aproximadamente 15x en este ejemplo. Claramente, esta desaceleración será una seria limitación para cargas de trabajo más grandes. En el "ejemplo de software del mundo real" mencionado aquí , observé una desaceleración de 80x.
La ejecución genera un archivo de datos de perfil llamado, callgrind.out.<pid>por ejemplo, callgrind.out.8554en mi caso. Vemos ese archivo con:
kcachegrind callgrind.out.8554
que muestra una GUI que contiene datos similares a la salida textual de gprof:
Además, si vamos a la parte inferior derecha de la pestaña "Gráfico de llamadas", vemos un gráfico de llamadas que podemos exportar haciendo clic derecho para obtener la siguiente imagen con cantidades irrazonables de borde blanco :-)
Creo que fastno se muestra en ese gráfico porque kcachegrind debe haber simplificado la visualización porque esa llamada toma muy poco tiempo, este será probablemente el comportamiento que desea en un programa real. El menú del botón derecho tiene algunas configuraciones para controlar cuándo eliminar dichos nodos, pero no pude hacer que mostrara una llamada tan corta después de un intento rápido. Si hago clic en fastla ventana de la izquierda, muestra un gráfico de llamadas fast, por lo que esa pila fue capturada. Nadie había encontrado todavía una manera de mostrar el gráfico de llamada de gráfico completo: hacer que callgrind muestre todas las llamadas de función en el gráfico de llamada kcachegrind
TODO en software C ++ complejo, veo algunas entradas de tipo <cycle N>, por ejemplo, <cycle 11>donde esperaría nombres de funciones, ¿qué significa eso? Me di cuenta de que hay un botón de "Detección de ciclos" para activarlo y desactivarlo, pero ¿qué significa?
perf desde linux-tools
perfparece utilizar exclusivamente mecanismos de muestreo de kernel de Linux. Esto hace que sea muy simple de configurar, pero tampoco totalmente preciso.
sudo apt install linux-tools
time perf record -g ./main.out 10000
Esto agregó 0.2s a la ejecución, por lo que estamos bien en cuanto al tiempo, pero aún no veo mucho interés, después de expandir el commonnodo con la flecha derecha del teclado:
TODO: ¿qué pasó con la -O3ejecución? ¿Es simplemente eso maybe_slowy fastfueron demasiado rápidos y no obtuvieron ninguna muestra? ¿Funciona bien con -O3programas más grandes que tardan más en ejecutarse? ¿Me perdí alguna opción de CLI? Descubrí que estaba a punto -Fde controlar la frecuencia de la muestra en Hertz, pero la aumenté al máximo permitido por defecto de -F 39500(podría aumentarse con sudo) y todavía no veo llamadas claras.
Pero esto tiene el inconveniente de que primero tiene que convertir los datos al formato de rastreo común, que se puede hacer perf data --to-ctf, pero debe habilitarse en el momento de la compilación / tener lo perfsuficientemente nuevo, cualquiera de los cuales no es el caso para el rendimiento en Ubuntu 18.04
Luego, podemos habilitar el generador de perfiles de CPU gperftools de dos maneras: en tiempo de ejecución o en tiempo de compilación.
En el tiempo de ejecución, tenemos que pasar establecer el LD_PRELOADpunto al libprofiler.soque puede encontrar locate libprofiler.so, por ejemplo, en mi sistema:
La mejor manera de ver estos datos que he encontrado hasta ahora es hacer que la salida de pprof tenga el mismo formato que kcachegrind toma como entrada (sí, la herramienta Valgrind-project-viewer-tool) y usar kcachegrind para ver eso:
Después de ejecutar cualquiera de esos métodos, obtenemos un prof.outarchivo de datos de perfil como salida. Podemos ver ese archivo gráficamente como un SVG con:
google-pprof --web main.out prof.out
que da como un gráfico de llamada familiar como otras herramientas, pero con la unidad torpe de número de muestras en lugar de segundos.
Alternativamente, también podemos obtener algunos datos textuales con:
google-pprof --text main.out prof.out
lo que da:
Using local file main.out.Using local file prof.out.Total:187 samples
187100.0%100.0%187100.0% common
00.0%100.0%187100.0% __libc_start_main
00.0%100.0%187100.0% _start
00.0%100.0%42.1% fast
00.0%100.0%187100.0% main
00.0%100.0%18397.9% maybe_slow
Por defecto, el registro perf utiliza el registro de puntero de cuadro. Los compiladores modernos no registran la dirección del marco y en su lugar usan el registro como un propósito general. La alternativa es compilar con -fno-omit-frame-pointerflag o usar una alternativa diferente: grabar con --call-graph "dwarf"o --call-graph "lbr"dependiendo de su escenario.
Jorge Bellon
5
Para programas de subproceso único, puede usar igprof , The Ignominous Profiler: https://igprof.org/ .
Es un perfilador de muestreo, en la línea de la ... larga ... respuesta de Mike Dunlavey, que envolverá los resultados en un árbol de pila de llamadas navegable, anotado con el tiempo o la memoria gastada en cada función, ya sea acumulativa o por función.
Parece interesante, pero no se compila con GCC 9.2. (Debian / Sid) Hice un problema en github.
Basile Starynkevitch
5
También vale la pena mencionar son
HPCToolkit ( http://hpctoolkit.org/ ): de código abierto, funciona para programas paralelos y tiene una GUI con la que puede ver los resultados de varias maneras
He usado HPCToolkit y VTune y son muy efectivos para encontrar el poste largo en la carpa y no necesitan que se vuelva a compilar su código (excepto que debe usar -g -O o RelWithDebInfo type build en CMake para obtener resultados significativos) . He oído que TAU tiene capacidades similares.
Estos son los dos métodos que uso para acelerar mi código:
Para aplicaciones vinculadas a la CPU:
Use un generador de perfiles en modo DEPURAR para identificar partes cuestionables de su código
Luego cambie al modo RELEASE y comente las secciones cuestionables de su código (colóquelo sin nada) hasta que vea cambios en el rendimiento.
Para aplicaciones vinculadas de E / S:
Use un generador de perfiles en modo LIBERACIÓN para identificar partes cuestionables de su código.
nótese bien
Si no tiene un perfilador, use el perfilador del pobre. Haga clic en pausa mientras depura su aplicación. La mayoría de los conjuntos de desarrolladores se dividirán en ensamblados con números de línea comentados. Es estadísticamente probable que aterrice en una región que está consumiendo la mayoría de sus ciclos de CPU.
Para la CPU, la razón para crear un perfil en el modo DEBUG es que si intentaste crear un perfil en el modo RELEASE , el compilador reducirá las matemáticas, vectorizará los bucles y las funciones en línea que tienden a englobar tu código en un desastre no asignable cuando se ensambla. Un desorden no asignable significa que su generador de perfiles no podrá identificar claramente lo que está tardando tanto porque el ensamblado puede no corresponder con el código fuente bajo optimización . Si necesita el rendimiento (p. Ej., Sensible al tiempo) del modo RELEASE , desactive las funciones del depurador según sea necesario para mantener un rendimiento utilizable.
Para el enlace de E / S, el generador de perfiles aún puede identificar operaciones de E / S en modo LIBERACIÓN porque las operaciones de E / S están vinculadas externamente a una biblioteca compartida (la mayoría de las veces) o, en el peor de los casos, darán como resultado un sistema. vector de interrupción de llamada (que también es fácilmente identificable por el generador de perfiles).
+1 El método del pobre hombre funciona igual de bien para E / S enlazado como para CPU enlazado, y recomiendo hacer todos los ajustes de rendimiento en modo DEBUG. Cuando termine de sintonizar, encienda RELEASE. Hará una mejora si el programa está vinculado a la CPU en su código. Aquí hay un video crudo pero corto del proceso.
Mike Dunlavey
3
No usaría las compilaciones DEBUG para perfilar el rendimiento. A menudo he visto que las partes críticas de rendimiento en el modo DEBUG están completamente optimizadas en el modo de lanzamiento. Otro problema es el uso de afirmaciones en el código de depuración que agregan ruido al rendimiento.
gast128
3
¿Leíste mi publicación? "Si necesita el rendimiento (p. Ej., Sensible al tiempo) del modo RELEASE, desactive las funciones del depurador según sea necesario para mantener un rendimiento utilizable", "Luego cambie al modo RELEASE y comente las secciones cuestionables de su código (Stub it with nothing) hasta que vea cambios en el rendimiento "? Dije que busque posibles áreas problemáticas en el modo de depuración y verifique esos problemas en el modo de liberación para evitar la trampa que mencionó.
Es multiplataforma y le permite no medir el rendimiento de su aplicación también en tiempo real. Incluso puedes acoplarlo con un gráfico en vivo. Descargo de responsabilidad completo: soy el autor.
Puede usar un marco de registro como, loguruya que incluye marcas de tiempo y tiempo de actividad total que se puede usar muy bien para crear perfiles:
En el trabajo tenemos una herramienta realmente agradable que nos ayuda a monitorear lo que queremos en términos de programación. Esto ha sido útil en numerosas ocasiones.
Está en C ++ y debe personalizarse según sus necesidades. Desafortunadamente no puedo compartir código, solo conceptos. Usas un "grande"volatile búfer que contiene marcas de tiempo e ID de evento que puede volcar post mortem o después de detener el sistema de registro (y volcar esto en un archivo, por ejemplo).
Usted recupera el llamado búfer grande con todos los datos y una pequeña interfaz lo analiza y muestra eventos con nombre (arriba / abajo + valor) como lo hace un osciloscopio con colores (configurados en el .hpparchivo).
Personaliza la cantidad de eventos generados para centrarse únicamente en lo que desea. Nos ayudó mucho para problemas de programación al tiempo que consumía la cantidad de CPU que queríamos en función de la cantidad de eventos registrados por segundo.
Necesitas 3 archivos:
toolname.hpp // interface
toolname.cpp // code
tool_events_id.hpp // Events ID
El concepto es definir eventos de tool_events_id.hppesa manera:
los probe función utiliza algunas líneas de ensamblaje para recuperar la marca de tiempo del reloj lo antes posible y luego establece una entrada en el búfer. También tenemos un incremento atómico para encontrar de forma segura un índice donde almacenar el evento de registro. Por supuesto, el buffer es circular.
Espero que la idea no se ofusque por la falta de código de muestra.
En realidad, un poco sorprendido, no muchos mencionaron sobre google / benchmark , aunque es un poco engorroso fijar el área específica del código, especialmente si la base del código es un poco grande, sin embargo, encontré esto realmente útil cuando se usa en combinación concallgrind
En mi humilde opinión, la clave aquí es identificar la pieza que está causando el cuello de botella. Sin embargo, primero trataría de responder las siguientes preguntas y elegiría la herramienta basada en eso
¿Es correcto mi algoritmo?
¿Hay cerraduras que están demostrando ser cuellos de botella?
¿hay una sección específica de código que demuestre ser la culpable?
¿Qué tal IO, manejado y optimizado?
valgrindcon la combinación de callrindy kcachegrinddebería proporcionar una estimación decente en los puntos anteriores y una vez que se haya establecido que hay problemas con alguna sección del código, sugeriría que un micro punto de referencia google benchmarksea un buen lugar para comenzar.
Use el -pgindicador cuando compile y vincule el código y ejecute el archivo ejecutable. Mientras se ejecuta este programa, los datos de creación de perfiles se recopilan en el archivo a.out.
Hay dos tipos diferentes de perfiles
1- Perfiles planos:
al ejecutar el comando gprog --flat-profile a.outse obtienen los siguientes datos
: qué porcentaje del tiempo total se dedicó a la función,
cuántos segundos se dedicaron a una función, incluidas y excluidas las llamadas a subfunciones,
el número de llamadas,
- el tiempo promedio por llamada.
2- gráfico que
nos perfila el comando gprof --graph a.outpara obtener los siguientes datos para cada función que incluye
- En cada sección, una función está marcada con un número de índice.
- Por encima de la función, hay una lista de funciones que llaman a la función.
- Debajo de la función, hay una lista de funciones que son llamadas por la función.
Como nadie mencionó Arm MAP, lo agregaría ya que personalmente he usado Map para perfilar un programa científico de C ++.
Arm MAP es el generador de perfiles para códigos C, C ++, Fortran y F90 paralelos, multiproceso o de un solo subproceso. Proporciona un análisis en profundidad y una localización precisa del cuello de botella en la línea de origen. A diferencia de la mayoría de los perfiladores, está diseñado para poder perfilar pthreads, OpenMP o MPI para código paralelo y enhebrado.
utiliza un software de depuración
¿cómo identificar dónde se ejecuta el código lentamente?
solo piense que tiene un obstáculo mientras está en movimiento, entonces disminuirá su velocidad
como las operaciones de bucle de reasignación no deseadas, desbordamientos de búfer, búsquedas, pérdidas de memoria, etc., consume más potencia de ejecución que afectará negativamente al rendimiento del código. Asegúrese de agregar -pg a la compilación antes de perfilar:
g++ your_prg.cpp -pgo cc my_program.cpp -g -pgsegún su compilador
No lo he probado todavía, pero he oído cosas buenas sobre google-perftools. Definitivamente vale la pena intentarlo.
valgrind --tool=callgrind ./(Your binary)
Generará un archivo llamado gmon.out o callgrind.out.x. Luego puede usar la herramienta kcachegrind o depurador para leer este archivo. Le dará un análisis gráfico de las cosas con resultados como qué líneas cuestan cuánto.
code
perfiladores. Sin embargo, la inversión prioritaria, el alias de caché, la contención de recursos, etc., pueden ser factores para la optimización y el rendimiento. Creo que la gente lee información en mi código lento . Las preguntas frecuentes hacen referencia a este hilo.Respuestas:
Si su objetivo es usar un generador de perfiles, use uno de los sugeridos.
Sin embargo, si tiene prisa y puede interrumpir manualmente su programa bajo el depurador mientras es subjetivamente lento, hay una manera simple de encontrar problemas de rendimiento.
Simplemente deténgalo varias veces, y cada vez mire la pila de llamadas. Si hay algún código que está desperdiciando un porcentaje del tiempo, 20% o 50% o lo que sea, esa es la probabilidad de que lo atrape en el acto en cada muestra. Entonces, ese es aproximadamente el porcentaje de muestras en el que lo verá. No se requieren conjeturas educadas. Si tiene una idea de cuál es el problema, esto lo probará o lo desaprobará.
Puede tener múltiples problemas de rendimiento de diferentes tamaños. Si limpia alguno de ellos, los restantes tomarán un porcentaje mayor y serán más fáciles de detectar en los pases posteriores. Este efecto de aumento , cuando se combina con múltiples problemas, puede conducir a factores de aceleración verdaderamente masivos.
Advertencia : los programadores tienden a ser escépticos de esta técnica a menos que la hayan usado ellos mismos. Dirán que los perfiladores le brindan esta información, pero eso solo es cierto si toman muestras de toda la pila de llamadas y luego le permiten examinar un conjunto aleatorio de muestras. (Los resúmenes son donde se pierde la información). Los gráficos de llamadas no le brindan la misma información, porque
También dirán que solo funciona en programas de juguetes, cuando en realidad funciona en cualquier programa, y parece funcionar mejor en programas más grandes, porque tienden a tener más problemas para encontrar. Dirán que a veces encuentra cosas que no son problemas, pero eso solo es cierto si ves algo una vez . Si ve un problema en más de una muestra, es real.
PD Esto también se puede hacer en programas de subprocesos múltiples si hay una manera de recopilar muestras de la pila de llamadas del grupo de subprocesos en un momento dado, como en Java.
PPS Como una generalidad aproximada, mientras más capas de abstracción tenga en su software, es más probable que descubra que esa es la causa de los problemas de rendimiento (y la oportunidad de acelerar).
Adicional : Puede que no sea obvio, pero la técnica de muestreo de pila funciona igualmente bien en presencia de recursividad. La razón es que el tiempo que se ahorraría al eliminar una instrucción se aproxima por la fracción de muestras que la contienen, independientemente de la cantidad de veces que pueda ocurrir dentro de una muestra.
Otra objeción que escucho a menudo es: " Se detendrá en algún lugar al azar, y perderá el verdadero problema ". Esto viene de tener un concepto previo de cuál es el verdadero problema. Una propiedad clave de los problemas de rendimiento es que desafían las expectativas. El muestreo te dice que algo es un problema, y tu primera reacción es de incredulidad. Eso es natural, pero puede estar seguro de que si encuentra un problema, es real y viceversa.
Agregado : Permítanme hacer una explicación bayesiana de cómo funciona. Supongamos que hay alguna instrucción
I
(llamada o no) que está en la pila de llamadas una fracciónf
del tiempo (y por lo tanto cuesta tanto). Por simplicidad, supongamos que no sabemos quéf
es, pero supongamos que es 0.1, 0.2, 0.3, ... 0.9, 1.0, y la probabilidad previa de cada una de estas posibilidades es 0.1, por lo que todos estos costos son igualmente probables a priori.Luego, supongamos que tomamos solo 2 muestras de pila y vemos instrucciones
I
en ambas muestras, observación designadao=2/2
. Esto nos da nuevas estimaciones de la frecuenciaf
deI
acuerdo con esto:La última columna dice que, por ejemplo, la probabilidad de que
f
> = 0.5 sea del 92%, por encima del supuesto anterior del 60%.Supongamos que los supuestos anteriores son diferentes. Supongamos que suponemos
P(f=0.1)
.991 (casi seguro), y todas las demás posibilidades son casi imposibles (0.001). En otras palabras, nuestra certeza previa es queI
es barata. Entonces obtenemos:Ahora dice que
P(f >= 0.5)
es 26%, por encima del supuesto anterior de 0.6%. Entonces Bayes nos permite actualizar nuestra estimación del costo probable deI
. Si la cantidad de datos es pequeña, no nos dice con precisión cuál es el costo, solo que es lo suficientemente grande como para que valga la pena arreglarlo.Otra forma de verlo se llama la Regla de Sucesión . Si lanzas una moneda 2 veces, y sale cara en ambas ocasiones, ¿qué te dice eso sobre el probable peso de la moneda? La forma respetada de responder es decir que es una distribución Beta, con un valor promedio
(number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%
.(La clave es que vemos
I
más de una vez. Si solo lo vemos una vez, eso no nos dice mucho, excepto quef
> 0.)Entonces, incluso un número muy pequeño de muestras puede decirnos mucho sobre el costo de las instrucciones que ve. (Y va a ver con una frecuencia de, en promedio, proporcional a su costo. Si
n
se toman muestras, yf
es el costo, entoncesI
van a aparecer ennf+/-sqrt(nf(1-f))
las muestras. Ejemplo,n=10
,f=0.3
, es decir3+/-1.4
muestras.)Agregado : para dar una idea intuitiva de la diferencia entre la medición y el muestreo aleatorio de la pila:
ahora hay perfiladores que muestrean la pila, incluso en el tiempo del reloj de pared, pero lo que sale son las mediciones (o ruta caliente, o punto caliente, desde el cual un "cuello de botella" puede esconderse fácilmente). Lo que no le muestran (y podrían fácilmente) son las muestras en sí mismas. Y si su objetivo es encontrar el cuello de botella, el número de ellos que necesita ver es, en promedio , 2 dividido por la fracción de tiempo que lleva. Entonces, si toma el 30% del tiempo, 2 / .3 = 6.7 muestras, en promedio, lo mostrarán, y la probabilidad de que 20 muestras lo muestren es 99.2%.
Aquí hay una ilustración poco convencional de la diferencia entre examinar mediciones y examinar muestras de pila. El cuello de botella podría ser una gran gota como esta, o numerosas pequeñas, no hay diferencia.
La medida es horizontal; le dice qué fracción de tiempo toman rutinas específicas. El muestreo es vertical. Si hay alguna forma de evitar lo que está haciendo todo el programa en ese momento, y si lo ve en una segunda muestra , ha encontrado el cuello de botella. Eso es lo que marca la diferencia: ver la razón completa del tiempo que se pasa, no solo cuánto.
fuente
Puedes usar Valgrind con las siguientes opciones
Generará un archivo llamado
callgrind.out.x
. Luego puede usar lakcachegrind
herramienta para leer este archivo. Le dará un análisis gráfico de las cosas con resultados como qué líneas cuestan cuánto.fuente
./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
gprof2dot
ahora está aquí: github.com/jrfonseca/gprof2dotSupongo que estás usando GCC. La solución estándar sería perfilar con gprof .
Asegúrese de agregar
-pg
a la compilación antes de perfilar:Todavía no lo he probado, pero he oído cosas buenas sobre google-perftools . Definitivamente vale la pena intentarlo.
Pregunta relacionada aquí .
Algunas otras palabras de moda si
gprof
no hacen el trabajo por usted: Valgrind , Intel VTune , Sun DTrace .fuente
Los nuevos núcleos (por ejemplo, los últimos núcleos de Ubuntu) vienen con las nuevas herramientas 'perf' (
apt-get install linux-tools
) AKA perf_events .¡Estos vienen con perfiladores de muestreo clásicos ( página de manual ), así como con el impresionante diagrama de tiempo !
Lo importante es que estas herramientas pueden ser la creación de perfiles del sistema y no solo la creación de perfiles de procesos: pueden mostrar la interacción entre subprocesos, procesos y el núcleo y le permiten comprender la programación y las dependencias de E / S entre procesos.
fuente
perf report
parece darme los nombres de las funciones con los padres de la llamada ... (por lo que es una especie de vista de mariposa invertida)gprof2dot
yperf script
. Muy buena herramienta!perf
existe en archive.li/9r927#selection-767.126-767.271 (¿Por qué los dioses por lo que decidió eliminar esa página de la base de conocimientos por lo que es más allá de mí ....)Usaría Valgrind y Callgrind como base para mi conjunto de herramientas de creación de perfiles. Lo importante es saber que Valgrind es básicamente una máquina virtual:
Callgrind es un generador de perfiles sobre eso. El principal beneficio es que no tiene que ejecutar su aplicación durante horas para obtener resultados confiables. Incluso una segunda carrera es suficiente para obtener resultados sólidos y confiables, ya que Callgrind es un generador de perfiles sin sondeo .
Otra herramienta construida sobre Valgrind es Massif. Lo uso para perfilar el uso de memoria del montón. Funciona muy bien Lo que hace es que le da instantáneas del uso de la memoria: información detallada QUÉ contiene QUÉ porcentaje de memoria, y QUIÉN lo había puesto allí. Dicha información está disponible en diferentes puntos de tiempo de ejecución de la aplicación.
fuente
La respuesta para ejecutar
valgrind --tool=callgrind
no está completa sin algunas opciones. Por lo general, no queremos perfilar 10 minutos de tiempo de inicio lento en Valgrind y queremos perfilar nuestro programa cuando está realizando alguna tarea.Entonces esto es lo que recomiendo. Ejecute el programa primero:
Ahora, cuando funciona y queremos comenzar a generar perfiles, debemos ejecutar en otra ventana:
Esto activa la creación de perfiles. Para apagarlo y detener toda la tarea, podemos usar:
Ahora tenemos algunos archivos llamados callgrind.out. * En el directorio actual. Para ver resultados de perfiles, use:
Recomiendo en la siguiente ventana hacer clic en el encabezado de la columna "Self", de lo contrario, muestra que "main ()" es la tarea que requiere más tiempo. "Self" muestra cuánto tiempo llevó cada función en sí, no junto con las personas dependientes.
fuente
CALLGRIND_TOGGLE_COLLECT
que habilitar / deshabilitar la recopilación mediante programación; ver stackoverflow.com/a/13700817/288875Esta es una respuesta a la respuesta Gprof de Nazgob .
He estado usando Gprof los últimos días y ya he encontrado tres limitaciones significativas, una de las cuales no he visto documentada en ningún otro lugar (todavía):
No funciona correctamente en código multiproceso, a menos que use una solución alternativa
El gráfico de llamadas se confunde con los punteros de función. Ejemplo: Tengo una función llamada
multithread()
que me permite realizar múltiples subprocesos de una función específica sobre una matriz específica (ambas pasadas como argumentos). Sin embargo, Gprof considera que todas las llamadasmultithread()
son equivalentes para calcular el tiempo que se pasa en los niños. Dado que algunas funciones que pasomultithread()
tardan mucho más que otras, mis gráficos de llamadas son en su mayoría inútiles. (Para aquellos que se preguntan si el problema es el enhebrado aquí: no,multithread()
opcionalmente, y en este caso, ejecuté todo secuencialmente solo en el hilo de llamada).Aquí dice que "... las cifras del número de llamadas se obtienen contando, no muestreando. Son completamente precisas ...". Sin embargo, encuentro que mi gráfico de llamadas me da 5345859132 + 784984078 como estadísticas de llamadas a mi función más llamada, donde se supone que el primer número son llamadas directas y las segundas llamadas recursivas (que son todas de sí mismo). Como esto implicaba que tenía un error, puse contadores largos (64 bits) en el código y volví a hacer lo mismo. Mis recuentos: 5345859132 directo y 78094395406 llamadas autorrecurrentes. Hay muchos dígitos allí, así que señalaré que las llamadas recursivas que mido son 78 mil millones, frente a 784 millones de Gprof: un factor de 100 diferentes. Ambas ejecuciones fueron de un solo subproceso y código no optimizado, uno compilado
-g
y el otro-pg
.Este fue GNU Gprof (GNU Binutils para Debian) 2.18.0.20080103 ejecutándose bajo Debian Lenny de 64 bits, si eso ayuda a alguien.
fuente
Use Valgrind, callgrind y kcachegrind:
genera callgrind.out.x. Léalo usando kcachegrind.
Use gprof (agregar -pg):
(no tan bueno para subprocesos múltiples, punteros de función)
Utiliza google-perftools:
Utiliza el muestreo de tiempo, se revelan los cuellos de botella de E / S y CPU.
Intel VTune es el mejor (gratis para fines educativos).
Otros: AMD Codeanalyst (desde que se reemplazó con AMD CodeXL), OProfile, herramientas 'perf' (apt-get install linux-tools)
fuente
Encuesta de técnicas de perfilado C ++
En esta respuesta, usaré varias herramientas diferentes para analizar algunos programas de prueba muy simples, a fin de comparar concretamente cómo funcionan esas herramientas.
El siguiente programa de prueba es muy simple y hace lo siguiente:
main
llamadasfast
ymaybe_slow
3 veces, una de lasmaybe_slow
llamadas es lentaLa llamada lenta de
maybe_slow
es 10 veces más larga y domina el tiempo de ejecución si consideramos las llamadas a la función secundariacommon
. Idealmente, la herramienta de creación de perfiles podrá indicarnos la llamada lenta específica.ambos
fast
ymaybe_slow
callcommon
, que representa la mayor parte de la ejecución del programaLa interfaz del programa es:
y el programa hace
O(n^2)
bucles en total.seed
es solo para obtener una salida diferente sin afectar el tiempo de ejecución.C Principal
gprof
gprof requiere recompilar el software con instrumentación, y también utiliza un enfoque de muestreo junto con esa instrumentación. Por lo tanto, logra un equilibrio entre la precisión (el muestreo no siempre es completamente exacto y puede omitir funciones) y la ralentización de la ejecución (la instrumentación y el muestreo son técnicas relativamente rápidas que no ralentizan mucho la ejecución).
gprof está integrado en GCC / binutils, por lo que todo lo que tenemos que hacer es compilar con la
-pg
opción para habilitar gprof. Luego ejecutamos el programa normalmente con un parámetro CLI de tamaño que produce una ejecución de duración razonable de unos segundos (10000
):Por razones educativas, también haremos una ejecución sin optimizaciones habilitadas. Tenga en cuenta que esto es inútil en la práctica, ya que normalmente solo le importa optimizar el rendimiento del programa optimizado:
Primero,
time
nos dice que el tiempo de ejecución con y sin-pg
fue el mismo, lo cual es genial: ¡no hay desaceleración! Sin embargo, he visto cuentas de ralentizaciones 2x - 3x en software complejo, por ejemplo, como se muestra en este ticket .Debido a que compilamos
-pg
, ejecutar el programa produce un archivogmon.out
que contiene los datos de creación de perfiles.Podemos observar ese archivo gráficamente con
gprof2dot
lo solicitado en: ¿Es posible obtener una representación gráfica de los resultados de gprof?Aquí, la
gprof
herramienta lee lagmon.out
información de rastreo y genera un informe legible por humanosmain.gprof
, quegprof2dot
luego se lee para generar un gráfico.La fuente de gprof2dot está en: https://github.com/jrfonseca/gprof2dot
Observamos lo siguiente para la
-O0
carrera:y para la
-O3
carrera:La
-O0
salida es bastante autoexplicativa. Por ejemplo, muestra que las 3maybe_slow
llamadas y sus llamadas secundarias ocupan el 97.56% del tiempo de ejecución total, aunque la ejecución demaybe_slow
sí mismo sin hijos representa el 0.00% del tiempo de ejecución total, es decir, casi todo el tiempo dedicado a esa función se gastó en niño llama.TODO: ¿por qué
main
falta en la-O3
salida, aunque puedo verlo en unbt
en GDB? Falta la función de la salida de GProf . Creo que es porque gprof también está basado en muestreo además de su instrumentación compilada, y-O3
main
es demasiado rápido y no tiene muestras.Elijo la salida SVG en lugar de PNG porque la SVG se puede buscar con Ctrl + F y el tamaño del archivo puede ser aproximadamente 10 veces más pequeño. Además, el ancho y la altura de la imagen generada pueden ser enormes con decenas de miles de píxeles para software complejo, y GNOME
eog
3.28.1 produce errores en ese caso para PNG, mientras que mi navegador abre automáticamente los SVG. Sin embargo, gimp 2.8 funcionó bien, ver también:pero incluso así, arrastrará la imagen mucho para encontrar lo que desea, vea, por ejemplo, esta imagen de un ejemplo de software "real" tomado de este ticket :
¿Puedes encontrar la pila de llamadas más crítica fácilmente con todas esas pequeñas líneas de espagueti sin clasificar que se cruzan entre sí? Puede haber mejores
dot
opciones, estoy seguro, pero no quiero ir allí ahora. Lo que realmente necesitamos es un visor dedicado adecuado para él, pero aún no he encontrado uno:Sin embargo, puede usar el mapa de colores para mitigar un poco esos problemas. Por ejemplo, en la gran imagen anterior, finalmente logré encontrar el camino crítico a la izquierda cuando hice la brillante deducción de que el verde viene después del rojo, seguido finalmente por un azul más y más oscuro.
Alternativamente, también podemos observar la salida de texto de la
gprof
herramienta binutils incorporada que guardamos previamente en:Por defecto, esto produce una salida extremadamente detallada que explica lo que significan los datos de salida. Como no puedo explicarlo mejor que eso, te dejaré leerlo tú mismo.
Una vez que haya entendido el formato de salida de datos, puede reducir la verbosidad para mostrar solo los datos sin el tutorial con la
-b
opción:En nuestro ejemplo, los resultados fueron para
-O0
:y para
-O3
:Como un resumen muy rápido para cada sección, por ejemplo:
se centra en la función que queda sangrada (
maybe_flow
).[3]
es la identificación de esa función. Encima de la función, están sus llamadores, y debajo de ella los callees.Para
-O3
, vea aquí como en la salida gráfica quemaybe_slow
yfast
no tiene un padre conocido, que es lo que la documentación dice que<spontaneous>
significa.No estoy seguro de si hay una buena manera de hacer perfiles línea por línea con gprof: el tiempo `gprof` empleado en líneas de código particulares
valgrind callgrind
valgrind ejecuta el programa a través de la máquina virtual valgrind. Esto hace que la creación de perfiles sea muy precisa, pero también produce una gran desaceleración del programa. También he mencionado kcachegrind anteriormente en: Herramientas para obtener un gráfico de llamada de función pictórica de código
callgrind es la herramienta de valgrind para perfilar código y kcachegrind es un programa de KDE que puede visualizar la salida de cachegrind.
Primero tenemos que eliminar la
-pg
bandera para volver a la compilación normal, de lo contrario, la ejecución realmente fallaProfiling timer expired
, y sí, esto es tan común que lo hice y había una pregunta de desbordamiento de pila.Entonces compilamos y ejecutamos como:
Habilito
--dump-instr=yes --collect-jumps=yes
porque esto también volca la información que nos permite ver un desglose del rendimiento por línea de ensamblaje, a un costo general agregado relativamente pequeño.De buenas a primeras,
time
nos dice que el programa tardó 29,5 segundos en ejecutarse, por lo que tuvimos una desaceleración de aproximadamente 15x en este ejemplo. Claramente, esta desaceleración será una seria limitación para cargas de trabajo más grandes. En el "ejemplo de software del mundo real" mencionado aquí , observé una desaceleración de 80x.La ejecución genera un archivo de datos de perfil llamado,
callgrind.out.<pid>
por ejemplo,callgrind.out.8554
en mi caso. Vemos ese archivo con:que muestra una GUI que contiene datos similares a la salida textual de gprof:
Además, si vamos a la parte inferior derecha de la pestaña "Gráfico de llamadas", vemos un gráfico de llamadas que podemos exportar haciendo clic derecho para obtener la siguiente imagen con cantidades irrazonables de borde blanco :-)
Creo que
fast
no se muestra en ese gráfico porque kcachegrind debe haber simplificado la visualización porque esa llamada toma muy poco tiempo, este será probablemente el comportamiento que desea en un programa real. El menú del botón derecho tiene algunas configuraciones para controlar cuándo eliminar dichos nodos, pero no pude hacer que mostrara una llamada tan corta después de un intento rápido. Si hago clic enfast
la ventana de la izquierda, muestra un gráfico de llamadasfast
, por lo que esa pila fue capturada. Nadie había encontrado todavía una manera de mostrar el gráfico de llamada de gráfico completo: hacer que callgrind muestre todas las llamadas de función en el gráfico de llamada kcachegrindTODO en software C ++ complejo, veo algunas entradas de tipo
<cycle N>
, por ejemplo,<cycle 11>
donde esperaría nombres de funciones, ¿qué significa eso? Me di cuenta de que hay un botón de "Detección de ciclos" para activarlo y desactivarlo, pero ¿qué significa?perf
desdelinux-tools
perf
parece utilizar exclusivamente mecanismos de muestreo de kernel de Linux. Esto hace que sea muy simple de configurar, pero tampoco totalmente preciso.Esto agregó 0.2s a la ejecución, por lo que estamos bien en cuanto al tiempo, pero aún no veo mucho interés, después de expandir el
common
nodo con la flecha derecha del teclado:Entonces trato de comparar el
-O0
programa para ver si eso muestra algo, y solo ahora, por fin, veo un gráfico de llamadas:TODO: ¿qué pasó con la
-O3
ejecución? ¿Es simplemente esomaybe_slow
yfast
fueron demasiado rápidos y no obtuvieron ninguna muestra? ¿Funciona bien con-O3
programas más grandes que tardan más en ejecutarse? ¿Me perdí alguna opción de CLI? Descubrí que estaba a punto-F
de controlar la frecuencia de la muestra en Hertz, pero la aumenté al máximo permitido por defecto de-F 39500
(podría aumentarse consudo
) y todavía no veo llamadas claras.Una cosa genial
perf
es la herramienta FlameGraph de Brendan Gregg, que muestra los tiempos de la pila de llamadas de una manera muy ordenada que le permite ver rápidamente las grandes llamadas. La herramienta está disponible en: https://github.com/brendangregg/FlameGraph y también se menciona en su tutorial de perf en: http://www.brendangregg.com/perf.html#FlameGraphs Cuando corríperf
sinsudo
lo conseguíERROR: No stack counts found
por ahora lo haré consudo
:pero en un programa tan simple, la salida no es muy fácil de entender, ya que no podemos ver fácilmente
maybe_slow
nifast
en ese gráfico:En un ejemplo más complejo queda claro lo que significa el gráfico:
TODO hay un registro de
[unknown]
funciones en ese ejemplo, ¿por qué es eso?Otras interfaces de interfaz gráfica de usuario que pueden valer la pena incluyen:
Complemento Eclipse Trace Compass: https://www.eclipse.org/tracecompass/
Pero esto tiene el inconveniente de que primero tiene que convertir los datos al formato de rastreo común, que se puede hacer
perf data --to-ctf
, pero debe habilitarse en el momento de la compilación / tener loperf
suficientemente nuevo, cualquiera de los cuales no es el caso para el rendimiento en Ubuntu 18.04https://github.com/KDAB/hotspot
La desventaja de esto es que parece que no hay un paquete de Ubuntu, y su construcción requiere Qt 5.10, mientras que Ubuntu 18.04 está en Qt 5.9.
gperftools
Anteriormente llamado "Google Performance Tools", fuente: https://github.com/gperftools/gperftools Basado en muestras.
Primero instale gperftools con:
Luego, podemos habilitar el generador de perfiles de CPU gperftools de dos maneras: en tiempo de ejecución o en tiempo de compilación.
En el tiempo de ejecución, tenemos que pasar establecer el
LD_PRELOAD
punto allibprofiler.so
que puede encontrarlocate libprofiler.so
, por ejemplo, en mi sistema:Alternativamente, podemos construir la biblioteca en el momento del enlace, distribuyendo el paso
LD_PRELOAD
en tiempo de ejecución:Ver también: gperftools - archivo de perfil no volcado
La mejor manera de ver estos datos que he encontrado hasta ahora es hacer que la salida de pprof tenga el mismo formato que kcachegrind toma como entrada (sí, la herramienta Valgrind-project-viewer-tool) y usar kcachegrind para ver eso:
Después de ejecutar cualquiera de esos métodos, obtenemos un
prof.out
archivo de datos de perfil como salida. Podemos ver ese archivo gráficamente como un SVG con:que da como un gráfico de llamada familiar como otras herramientas, pero con la unidad torpe de número de muestras en lugar de segundos.
Alternativamente, también podemos obtener algunos datos textuales con:
lo que da:
Ver también: Cómo usar las herramientas de Google Perf
Probado en Ubuntu 18.04, gprof2dot 2019.11.30, valgrind 3.13.0, perf 4.15.18, Linux kernel 4.15.0, FLameGraph 1a0dc6985aad06e76857cf2a354bd5ba0c9ce96b, gperftools 2.5-2.
fuente
-fno-omit-frame-pointer
flag o usar una alternativa diferente: grabar con--call-graph "dwarf"
o--call-graph "lbr"
dependiendo de su escenario.Para programas de subproceso único, puede usar igprof , The Ignominous Profiler: https://igprof.org/ .
Es un perfilador de muestreo, en la línea de la ... larga ... respuesta de Mike Dunlavey, que envolverá los resultados en un árbol de pila de llamadas navegable, anotado con el tiempo o la memoria gastada en cada función, ya sea acumulativa o por función.
fuente
También vale la pena mencionar son
He usado HPCToolkit y VTune y son muy efectivos para encontrar el poste largo en la carpa y no necesitan que se vuelva a compilar su código (excepto que debe usar -g -O o RelWithDebInfo type build en CMake para obtener resultados significativos) . He oído que TAU tiene capacidades similares.
fuente
Estos son los dos métodos que uso para acelerar mi código:
Para aplicaciones vinculadas a la CPU:
Para aplicaciones vinculadas de E / S:
nótese bien
Si no tiene un perfilador, use el perfilador del pobre. Haga clic en pausa mientras depura su aplicación. La mayoría de los conjuntos de desarrolladores se dividirán en ensamblados con números de línea comentados. Es estadísticamente probable que aterrice en una región que está consumiendo la mayoría de sus ciclos de CPU.
Para la CPU, la razón para crear un perfil en el modo DEBUG es que si intentaste crear un perfil en el modo RELEASE , el compilador reducirá las matemáticas, vectorizará los bucles y las funciones en línea que tienden a englobar tu código en un desastre no asignable cuando se ensambla. Un desorden no asignable significa que su generador de perfiles no podrá identificar claramente lo que está tardando tanto porque el ensamblado puede no corresponder con el código fuente bajo optimización . Si necesita el rendimiento (p. Ej., Sensible al tiempo) del modo RELEASE , desactive las funciones del depurador según sea necesario para mantener un rendimiento utilizable.
Para el enlace de E / S, el generador de perfiles aún puede identificar operaciones de E / S en modo LIBERACIÓN porque las operaciones de E / S están vinculadas externamente a una biblioteca compartida (la mayoría de las veces) o, en el peor de los casos, darán como resultado un sistema. vector de interrupción de llamada (que también es fácilmente identificable por el generador de perfiles).
fuente
Puede usar la biblioteca iprof:
https://gitlab.com/Neurochrom/iprof
https://github.com/Neurochrom/iprof
Es multiplataforma y le permite no medir el rendimiento de su aplicación también en tiempo real. Incluso puedes acoplarlo con un gráfico en vivo. Descargo de responsabilidad completo: soy el autor.
fuente
Puede usar un marco de registro como,
loguru
ya que incluye marcas de tiempo y tiempo de actividad total que se puede usar muy bien para crear perfiles:fuente
En el trabajo tenemos una herramienta realmente agradable que nos ayuda a monitorear lo que queremos en términos de programación. Esto ha sido útil en numerosas ocasiones.
Está en C ++ y debe personalizarse según sus necesidades. Desafortunadamente no puedo compartir código, solo conceptos. Usas un "grande"
volatile
búfer que contiene marcas de tiempo e ID de evento que puede volcar post mortem o después de detener el sistema de registro (y volcar esto en un archivo, por ejemplo).Usted recupera el llamado búfer grande con todos los datos y una pequeña interfaz lo analiza y muestra eventos con nombre (arriba / abajo + valor) como lo hace un osciloscopio con colores (configurados en el
.hpp
archivo).Personaliza la cantidad de eventos generados para centrarse únicamente en lo que desea. Nos ayudó mucho para problemas de programación al tiempo que consumía la cantidad de CPU que queríamos en función de la cantidad de eventos registrados por segundo.
Necesitas 3 archivos:
El concepto es definir eventos de
tool_events_id.hpp
esa manera:También define algunas funciones en
toolname.hpp
:En cualquier parte de su código puede usar:
los
probe
función utiliza algunas líneas de ensamblaje para recuperar la marca de tiempo del reloj lo antes posible y luego establece una entrada en el búfer. También tenemos un incremento atómico para encontrar de forma segura un índice donde almacenar el evento de registro. Por supuesto, el buffer es circular.Espero que la idea no se ofusque por la falta de código de muestra.
fuente
En realidad, un poco sorprendido, no muchos mencionaron sobre google / benchmark , aunque es un poco engorroso fijar el área específica del código, especialmente si la base del código es un poco grande, sin embargo, encontré esto realmente útil cuando se usa en combinación con
callgrind
En mi humilde opinión, la clave aquí es identificar la pieza que está causando el cuello de botella. Sin embargo, primero trataría de responder las siguientes preguntas y elegiría la herramienta basada en eso
valgrind
con la combinación decallrind
ykcachegrind
debería proporcionar una estimación decente en los puntos anteriores y una vez que se haya establecido que hay problemas con alguna sección del código, sugeriría que un micro punto de referenciagoogle benchmark
sea un buen lugar para comenzar.fuente
Use el
-pg
indicador cuando compile y vincule el código y ejecute el archivo ejecutable. Mientras se ejecuta este programa, los datos de creación de perfiles se recopilan en el archivo a.out.Hay dos tipos diferentes de perfiles
1- Perfiles planos:
al ejecutar el comando
gprog --flat-profile a.out
se obtienen los siguientes datos: qué porcentaje del tiempo total se dedicó a la función,
cuántos segundos se dedicaron a una función, incluidas y excluidas las llamadas a subfunciones,
el número de llamadas,
- el tiempo promedio por llamada.
2- gráfico que
nos perfila el comando
gprof --graph a.out
para obtener los siguientes datos para cada función que incluye- En cada sección, una función está marcada con un número de índice.
- Por encima de la función, hay una lista de funciones que llaman a la función.
- Debajo de la función, hay una lista de funciones que son llamadas por la función.
Para obtener más información, puede consultar https://sourceware.org/binutils/docs-2.32/gprof/
fuente
Como nadie mencionó Arm MAP, lo agregaría ya que personalmente he usado Map para perfilar un programa científico de C ++.
Arm MAP es el generador de perfiles para códigos C, C ++, Fortran y F90 paralelos, multiproceso o de un solo subproceso. Proporciona un análisis en profundidad y una localización precisa del cuello de botella en la línea de origen. A diferencia de la mayoría de los perfiladores, está diseñado para poder perfilar pthreads, OpenMP o MPI para código paralelo y enhebrado.
MAP es un software comercial.
fuente
utiliza un software de depuración ¿cómo identificar dónde se ejecuta el código lentamente?
solo piense que tiene un obstáculo mientras está en movimiento, entonces disminuirá su velocidad
como las operaciones de bucle de reasignación no deseadas, desbordamientos de búfer, búsquedas, pérdidas de memoria, etc., consume más potencia de ejecución que afectará negativamente al rendimiento del código. Asegúrese de agregar -pg a la compilación antes de perfilar:
g++ your_prg.cpp -pg
occ my_program.cpp -g -pg
según su compiladorNo lo he probado todavía, pero he oído cosas buenas sobre google-perftools. Definitivamente vale la pena intentarlo.
valgrind --tool=callgrind ./(Your binary)
Generará un archivo llamado gmon.out o callgrind.out.x. Luego puede usar la herramienta kcachegrind o depurador para leer este archivo. Le dará un análisis gráfico de las cosas con resultados como qué líneas cuestan cuánto.
creo que sí
fuente