profiling profiler gprof

profiling - Alternativas a gprof



profiler (7)

¿Qué otros programas hacen lo mismo que gprof?


Como no he visto nada acerca de perf que es una herramienta relativamente nueva para perfilar el kernel y las aplicaciones de usuario en Linux, decidí agregar esta información.

En primer lugar, este es un tutorial sobre perfiles de Linux con perf

Puede usar perf si su Kernel de Linux es mayor que 2.6.32 o oprofile si es anterior. Ambos programas no requieren que usted instrumente su programa (como requiere gprof ). Sin embargo, para obtener un gráfico de llamadas correctamente en perf , debe construir su programa con -fno-omit-frame-pointer . Por ejemplo: g++ -fno-omit-frame-pointer -O2 main.cpp .

Puede ver el análisis "en vivo" de su aplicación con perf top :

sudo perf top -p `pidof a.out` -K

O puede registrar datos de rendimiento de una aplicación en ejecución y analizarlos después de eso:

1) Para registrar datos de rendimiento:

perf record -p `pidof a.out`

o para grabar durante 10 segundos:

perf record -p `pidof a.out` sleep 10

o para grabar con el gráfico de llamadas ()

perf record -g -p `pidof a.out`

2) Analizar los datos grabados

perf report --stdio perf report --stdio --sort=dso -g none perf report --stdio -g none perf report --stdio -g

O puede registrar los datos de rendimiento de una aplicación y analizarlos después de eso simplemente iniciando la aplicación de esta manera y esperando a que salga:

perf record ./a.out

Este es un ejemplo de perfil de un programa de prueba

El programa de prueba está en el archivo main.cpp (pondré main.cpp en la parte inferior del mensaje):

Lo compilo de esta manera:

g++ -m64 -fno-omit-frame-pointer -g main.cpp -L. -ltcmalloc_minimal -o my_test

Utilizo libmalloc_minimial.so ya que está compilado con -fno-omit-frame-pointer mientras que libc malloc parece compilarse sin esta opción. Luego ejecuto mi programa de prueba

./my_test 100000000

Luego registro los datos de rendimiento de un proceso en ejecución:

perf record -g -p `pidof my_test` -o ./my_test.perf.data sleep 30

Luego analizo la carga por módulo:

informe de rendimiento --stdio -g none --sort comm, dso -i ./my_test.perf.data

# Overhead Command Shared Object # ........ ....... ............................ # 70.06% my_test my_test 28.33% my_test libtcmalloc_minimal.so.0.1.0 1.61% my_test [kernel.kallsyms]

Luego se analiza la carga por función:

informe de rendimiento --stdio -g none -i ./my_test.perf.data | c ++ filt

# Overhead Command Shared Object Symbol # ........ ....... ............................ ........................... # 29.30% my_test my_test [.] f2(long) 29.14% my_test my_test [.] f1(long) 15.17% my_test libtcmalloc_minimal.so.0.1.0 [.] operator new(unsigned long) 13.16% my_test libtcmalloc_minimal.so.0.1.0 [.] operator delete(void*) 9.44% my_test my_test [.] process_request(long) 1.01% my_test my_test [.] operator delete(void*)@plt 0.97% my_test my_test [.] operator new(unsigned long)@plt 0.20% my_test my_test [.] main 0.19% my_test [kernel.kallsyms] [k] apic_timer_interrupt 0.16% my_test [kernel.kallsyms] [k] _spin_lock 0.13% my_test [kernel.kallsyms] [k] native_write_msr_safe and so on ...

Luego se analizan las cadenas de llamadas:

informe de rendimiento --stdio -g gráfico -i ./my_test.perf.data | c ++ filt

# Overhead Command Shared Object Symbol # ........ ....... ............................ ........................... # 29.30% my_test my_test [.] f2(long) | --- f2(long) | --29.01%-- process_request(long) main __libc_start_main 29.14% my_test my_test [.] f1(long) | --- f1(long) | |--15.05%-- process_request(long) | main | __libc_start_main | --13.79%-- f2(long) process_request(long) main __libc_start_main 15.17% my_test libtcmalloc_minimal.so.0.1.0 [.] operator new(unsigned long) | --- operator new(unsigned long) | |--11.44%-- f1(long) | | | |--5.75%-- process_request(long) | | main | | __libc_start_main | | | --5.69%-- f2(long) | process_request(long) | main | __libc_start_main | --3.01%-- process_request(long) main __libc_start_main 13.16% my_test libtcmalloc_minimal.so.0.1.0 [.] operator delete(void*) | --- operator delete(void*) | |--9.13%-- f1(long) | | | |--4.63%-- f2(long) | | process_request(long) | | main | | __libc_start_main | | | --4.51%-- process_request(long) | main | __libc_start_main | |--3.05%-- process_request(long) | main | __libc_start_main | --0.80%-- f2(long) process_request(long) main __libc_start_main 9.44% my_test my_test [.] process_request(long) | --- process_request(long) | --9.39%-- main __libc_start_main 1.01% my_test my_test [.] operator delete(void*)@plt | --- operator delete(void*)@plt 0.97% my_test my_test [.] operator new(unsigned long)@plt | --- operator new(unsigned long)@plt 0.20% my_test my_test [.] main 0.19% my_test [kernel.kallsyms] [k] apic_timer_interrupt 0.16% my_test [kernel.kallsyms] [k] _spin_lock and so on ...

Entonces, en este punto, sabes dónde pasa tu tiempo el programa.

Y esto es main.cpp para la prueba:

#include <stdio.h> #include <stdlib.h> #include <time.h> time_t f1(time_t time_value) { for (int j =0; j < 10; ++j) { ++time_value; if (j%5 == 0) { double *p = new double; delete p; } } return time_value; } time_t f2(time_t time_value) { for (int j =0; j < 40; ++j) { ++time_value; } time_value=f1(time_value); return time_value; } time_t process_request(time_t time_value) { for (int j =0; j < 10; ++j) { int *p = new int; delete p; for (int m =0; m < 10; ++m) { ++time_value; } } for (int i =0; i < 10; ++i) { time_value=f1(time_value); time_value=f2(time_value); } return time_value; } int main(int argc, char* argv2[]) { int number_loops = argc > 1 ? atoi(argv2[1]) : 1; time_t time_value = time(0); printf("number loops %d/n", number_loops); printf("time_value: %d/n", time_value ); for (int i =0; i < number_loops; ++i) { time_value = process_request(time_value); } printf("time_value: %ld/n", time_value ); return 0; }


Eche un vistazo a Sysprof .

Su distribución puede tenerlo ya.


Prueba OProfile . Es una herramienta mucho mejor para perfilar tu código. También sugeriría Intel VTune .

Las dos herramientas anteriores pueden reducir el tiempo empleado en una determinada línea de código, anotar su código, mostrar el ensamblaje y la cantidad de instrucción que requiere. Además de la métrica de tiempo, también puede consultar contadores específicos, es decir, hits de caché, etc.

A diferencia de gprof, puede perfilar cualquier proceso / binario que se ejecute en su sistema usando cualquiera de los dos.



Valgrind tiene un generador de perfiles de instrucciones con un visualizador muy agradable llamado KCacheGrind . Como Mike Dunlavey recomienda, Valgrind cuenta la fracción de instrucciones para las cuales un procedimiento está activo en la pila, aunque lamento decir que parece confundirse en presencia de recursión mutua. Pero el visualizador es muy agradable y está a años luz de gprof .



gprof (leer el artículo) existe por razones históricas. Si crees que te ayudará a encontrar problemas de rendimiento, nunca se anunció como tal. Esto es lo que dice el periódico:

El perfil se puede usar para comparar y evaluar los costos de varias implementaciones.

No dice que pueda usarse para identificar las diversas implementaciones que se evaluarán, aunque implica que podría, bajo circunstancias especiales:

especialmente si se encuentra que pequeñas porciones del programa dominan su tiempo de ejecución.

¿Qué pasa con los problemas que no están tan localizados? ¿Eso no importa? No coloque expectativas en gprof que nunca fueron reclamadas. Es solo una herramienta de medición, y solo de operaciones vinculadas a CPU.

Prueba esto en su lugar.
Aquí hay un ejemplo de una aceleración 44x.
Aquí hay una aceleración de 730x.
Aquí hay una demostración de video de 8 minutos.
Aquí hay una explicación de las estadísticas.
Aquí hay una respuesta a las críticas.

Hay una simple observación sobre los programas. En una ejecución determinada, cada instrucción es responsable de una fracción del tiempo total (especialmente instrucciones de call ), en el sentido de que si no estuviera allí, el tiempo no se gastaría. Durante ese tiempo, la instrucción está en la pila **. Cuando eso se comprende, puedes ver eso:

gprof encarna ciertos mitos sobre el rendimiento, tales como:

  1. ese contador de muestras del programa es útil.
    Solo es útil si tiene un cuello de botella de puntos críticos innecesarios, como una especie de burbuja de una gran variedad de valores escalares. Tan pronto como usted, por ejemplo, lo cambie a un género usando string-compare, sigue siendo un cuello de botella, pero el muestreo de contador de programa no lo verá porque ahora el punto de acceso está en la comparación de cadenas. Por otro lado, si fuera a muestrear el contador de programa extendido (la pila de llamadas), se muestra claramente el punto en el que se llama a la comparación de cadenas, el bucle de ordenación. De hecho, gprof fue un intento de remediar las limitaciones del muestreo de solo PC.

  2. que las funciones de temporización son más importantes que capturar líneas de código que consumen mucho tiempo.
    La razón de ese mito es que gprof no fue capaz de capturar muestras de la pila, por lo tanto, multiplica las funciones, cuenta sus invocaciones e intenta capturar el gráfico de llamadas. Sin embargo, una vez que se identifica una función costosa, aún necesita buscar dentro de ella las líneas que son responsables del tiempo. Si hubiera muestras de pila que no necesitaría buscar, esas líneas estarían en las muestras. (Una función típica puede tener 100 - 1000 instrucciones. Una llamada a la función es 1 instrucción, por lo que algo que localiza llamadas costosas es de 2 a 3 órdenes de magnitud más preciso).

  3. que el gráfico de llamadas es importante.
    Lo que necesita saber sobre un programa no es dónde pasa su tiempo, sino por qué . Cuando pasa tiempo en una función, cada línea de código en la pila da un enlace en la cadena de razonamiento de por qué está allí. Si solo puede ver parte de la pila, solo puede ver parte del motivo, por lo que no puede determinar con certeza si ese momento es realmente necesario. ¿Qué te dice el gráfico de llamadas? Cada arco te dice que alguna función A estaba en el proceso de llamar a alguna función B durante una fracción del tiempo. Incluso si A tiene solo una de esas líneas de código que llama a B, esa línea solo da una pequeña parte de la razón por la cual. Si tienes la suerte, tal vez esa línea tiene una mala razón. Por lo general, necesita ver varias líneas simultáneas para encontrar una razón pobre si está allí. Si A llama a B en más de un lugar, entonces le dice aún menos.

  4. esa recursión es un problema complicado y confuso.
    Eso es solo porque gprof y otros perfiladores perciben la necesidad de generar un gráfico de llamadas y luego atribuir tiempos a los nodos. Si uno tiene muestras de la pila, el tiempo-costo de cada línea de código que aparece en las muestras es un número muy simple: la fracción de muestras en la que se encuentra. Si hay recursividad, una línea determinada puede aparecer más de una vez en una muestra. No importa. Supongamos que las muestras se toman cada N ms, y la línea aparece en F% de ellas (individualmente o no). Si se puede hacer que esa línea no tarde (por ejemplo, eliminándola o ramificándola), esas muestras desaparecerían y el tiempo se reduciría en un F%.

  5. esa precisión de la medida del tiempo (y por lo tanto una gran cantidad de muestras) es importante.
    Piensa en ello un segundo. Si una línea de código está en 3 de cada 5 muestras, entonces si pudiera disparar como una bombilla, eso es aproximadamente un 60% menos de tiempo que se usaría. Ahora, sabes que si hubieras tomado 5 muestras diferentes, es posible que solo la hayas visto 2 veces, o hasta 4. De modo que una medición del 60% se parece más a un rango general del 40% al 80%. Si fuera solo el 40%, ¿diría que no vale la pena solucionar el problema? Entonces, ¿cuál es el punto de precisión del tiempo, cuando lo que realmente quieres es encontrar los problemas ? 500 o 5000 muestras habrían medido el problema con mayor precisión, pero no lo hubieran encontrado con mayor precisión.

  6. ese recuento de invocaciones de declaración o función es útil.
    Supongamos que sabe que una función ha sido llamada 1000 veces. ¿Puedes decir a partir de eso qué fracción de tiempo cuesta? También necesita saber cuánto tiempo lleva correr, en promedio, multiplicarlo por el recuento, y dividir por el tiempo total. El tiempo de invocación promedio puede variar de nanosegundos a segundos, por lo que el recuento solo no dice mucho. Si hay muestras de pila, el costo de una rutina o de cualquier extracto es solo la fracción de las muestras en las que se encuentra. Esa fracción de tiempo es lo que, en principio, se podría guardar si se pudiera hacer que la rutina o el enunciado no demoraran nada, de modo que eso es lo que tiene una relación más directa con el rendimiento.

  7. que las muestras no necesitan tomarse cuando están bloqueadas
    Las razones de este mito son dobles: 1) que el muestreo de PC no tiene sentido cuando el programa está esperando, y 2) la preocupación por la precisión del tiempo. Sin embargo, para (1) el programa puede estar esperando algo que solicitó, como E / S de archivo, que necesita saber y qué muestras de pila revelan. (Obviamente, desea excluir muestras mientras espera la entrada del usuario.) Para (2) si el programa está esperando simplemente debido a la competencia con otros procesos, eso probablemente ocurra de manera bastante aleatoria mientras se está ejecutando. Entonces, aunque el programa puede tomar más tiempo, eso no tendrá un gran efecto en la estadística que importa, el porcentaje de tiempo que las declaraciones están en la pila.

  8. ese "tiempo del uno mismo" importa
    El auto tiempo solo tiene sentido si está midiendo en el nivel de función, no en el nivel de línea, y cree que necesita ayuda para discernir si el tiempo de la función entra en cómputos puramente locales versus en rutinas llamadas. Si se resume en el nivel de línea, una línea representa el tiempo propio si está al final de la pila, de lo contrario, representa el tiempo inclusivo. De cualquier forma, lo que cueste es el porcentaje de muestras de la chimenea en las que se encuentra, de modo que lo ubique en cualquier caso.

  9. que las muestras deben tomarse a alta frecuencia
    Esto se debe a la idea de que un problema de rendimiento puede ser de acción rápida y que las muestras deben ser frecuentes para poder alcanzarlo. Pero, si el problema está costando, 20%, por ejemplo, de un tiempo de ejecución total de 10 segundos (o lo que sea), entonces cada muestra en ese tiempo total tendrá un 20% de probabilidad de alcanzarlo, sin importar si el problema ocurre en una sola pieza como esta
    .....XXXXXXXX...........................
    .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^ (20 muestras, 4 resultados)
    o en muchas piezas pequeñas como esta
    X...X...XX.X.........X.....X....X.....
    .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^ (20 muestras, 3 resultados)
    De cualquier forma, la cantidad de visitas promediará aproximadamente 1 de cada 5, sin importar cuántas muestras se tomen o cuán pocas. (Promedio = 20 * 0.2 = 4. Desviación estándar = +/- sqrt (20 * 0.2 * 0.8) = 1.8)

  10. que estás tratando de encontrar el cuello de botella
    como si hubiera solo uno. Considere la siguiente línea de tiempo de ejecución: vxvWvzvWvxvWvYvWvxvWv.vWvxvWvYvW
    Consiste en un trabajo útil real, representado por . . Hay problemas de rendimiento vWxYz tomando 1/2, 1/4, 1/8, 1/16, 1/32 del tiempo, respectivamente. El muestreo encuentra v fácilmente. Se elimina, dejando
    xWzWxWYWxW.WxWYW
    Ahora el programa tarda la mitad de tiempo en ejecutarse, y ahora W tarda la mitad del tiempo, y se encuentra fácilmente. Se elimina, dejando
    xzxYx.xY
    Este proceso continúa, eliminando cada vez el mayor problema de rendimiento porcentual hasta que no se pueda encontrar nada para eliminar. Ahora lo único que se ejecuta es . , que se ejecuta en 1/32 del tiempo utilizado por el programa original. Este es el efecto de aumento , por el cual eliminar cualquier problema hace que el resto sea más grande, en porcentaje, porque el denominador se reduce.
    Otro punto crucial es que cada problema debe ser encontrado , faltando ninguno de los 5. Cualquier problema no encontrado y reparado reduce severamente la relación de aceleración final. Solo encontrar algunos, pero no todos, no es "lo suficientemente bueno".

AGREGADO: Solo me gustaría señalar una razón por la cual gprof es popular: se enseña, presumiblemente porque es gratis, fácil de enseñar y lleva mucho tiempo. Una búsqueda rápida en Google localiza algunas instituciones académicas que lo enseñan (o parecen):

berkeley bu clemson colorado duque earlham fsu indiana mit msu ncsa.illinois ncsu nyu ou princeton psu stanford ucsd umd utah utah utk wustl

** Con la excepción de otras formas de solicitar que se realice el trabajo, eso no deja un rastro que explique por qué , tal como mediante la publicación de mensajes.