¿Qué otros programas hacen lo mismo que gprof?
¿Qué otros programas hacen lo mismo que gprof?
Respuestas:
Valgrind tiene un generador de perfiles de recuento de instrucciones con un visualizador muy agradable llamado KCacheGrind . Como recomienda Mike Dunlavey, Valgrind cuenta la fracción de instrucciones para las que se realiza un procedimiento en la pila, aunque lamento decir que parece confundirse en presencia de recursividad mutua. Pero el visualizador es muy agradable y está a años luz por delante gprof
.
gprof (lea el documento) existe por razones históricas. Si cree que lo 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 diversas implementaciones.
No dice que se pueda usar para identificar las diversas implementaciones que se evaluarán, aunque sí implica que podría, en 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 ponga expectativas en gprof que nunca fueron reclamadas por ello. Es solo una herramienta de medición, y solo de operaciones vinculadas a la CPU.
Intenta esto en su lugar.
Aquí hay un ejemplo de una aceleración de 44x.
Aquí hay una aceleración 730x.
Aquí hay un video de demostración 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 dada, cada instrucción es responsable de una fracción del tiempo total (especialmente las call
instrucciones), 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 entiende, puedes ver que ...
gprof encarna ciertos mitos sobre el rendimiento, como:
ese programa de muestreo de contador es útil.
Solo es útil si tiene un cuello de botella de punto de acceso innecesario, como un tipo de burbuja de una gran variedad de valores escalares. Tan pronto como usted, por ejemplo, lo cambie a una clase usando la comparación de cadenas, sigue siendo un cuello de botella, pero el muestreo del contador del 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), el punto en el que se llama la comparación de cadenas, el bucle de clasificación, se muestra claramente. De hecho, gprof fue un intento de remediar las limitaciones del muestreo solo con PC.
que las funciones de temporización son más importantes que capturar líneas de código que requieren mucho tiempo.
La razón de ese mito es que gprof no pudo capturar muestras de la pila, por lo que en vez de eso funciona, cuenta sus invocaciones e intenta capturar el gráfico de llamadas. Sin embargo, una vez que se identifica una función costosa, aún debe buscar dentro de ella las líneas responsables del tiempo. Si hubiera muestras de pila que no necesitaría mirar, esas líneas estarían en las muestras. (Una función típica podría tener de 100 a 1000 instrucciones. Una llamada de función es 1 instrucción, por lo que algo que localiza llamadas costosas es 2-3 órdenes de magnitud más precisas).
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 proporciona 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 saber con certeza si ese tiempo es realmente necesario. ¿Qué te dice el gráfico de llamadas? Cada arco le dice que alguna función A estaba en 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. 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 te dice aún menos.
esa recursividad es un tema 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 costo de tiempo 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 dada puede aparecer más de una vez en una muestra.
No importa. Supongamos que se toman muestras cada N ms, y la línea aparece en F% de ellas (individualmente o no). Si se puede hacer que esa línea no tome tiempo (por ejemplo, eliminándola o ramificándola), esas muestras desaparecerían y el tiempo se reduciría en un F%.
esa precisión de la medición del tiempo (y, por lo tanto, una gran cantidad de muestras) es importante.
Piénsalo un segundo. Si una línea de código está en 3 muestras de cinco, entonces si pudiera dispararla como una bombilla, eso sería aproximadamente un 60% menos de tiempo que se usaría. Ahora, sabe que si hubiera tomado 5 muestras diferentes, podría haberlo visto solo 2 veces o hasta 4. De modo que la medición del 60% es más como 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 habrían encontrado con mayor precisión.
que es útil contar las invocaciones de sentencias o funciones.
Suponga que sabe que una función se ha llamado 1000 veces. ¿Puedes decir a partir de eso qué fracción de tiempo cuesta? También necesita saber cuánto tiempo tarda en ejecutarse, en promedio, multiplíquelo por el recuento y divídalo por el tiempo total. El tiempo promedio de invocación puede variar de nanosegundos a segundos, por lo que el recuento por sí solo no dice mucho. Si hay muestras de pila, el costo de una rutina o de cualquier declaración es solo la fracción de muestras en la que se encuentra. Esa fracción de tiempo es lo que, en principio, se podría ahorrar en general si se pudiera hacer que la rutina o la declaración no tomaran tiempo, por lo que es lo que tiene la relación más directa con el rendimiento.
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 de la sincronización. 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 presumiblemente ocurre de manera bastante aleatoria mientras se está ejecutando. Entonces, si bien el programa puede demorar más, eso no tendrá un gran efecto en la estadística que importa, el porcentaje de tiempo que las declaraciones están en la pila.
que el "tiempo propio" es importante El
tiempo propio solo tiene sentido si está midiendo a nivel de función, no a nivel de línea, y cree que necesita ayuda para discernir si el tiempo de función entra en un cálculo puramente local versus en rutinas llamadas. Si se resume a 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 manera, lo que cuesta es el porcentaje de muestras de pila en las que se encuentra, por lo que lo ubica en cualquier caso.
que las muestras deben tomarse a alta frecuencia
Esto surge de la idea de que un problema de rendimiento puede ser de acción rápida, y que las muestras deben ser frecuentes para resolverlo. Pero, si el problema está costando, 20%, digamos, de un tiempo de ejecución total de 10 segundos (o lo que sea), entonces cada muestra en ese tiempo total tendrá una probabilidad del 20% de alcanzarlo, sin importar si ocurre el problema en una sola pieza como esta
.....XXXXXXXX...........................
.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^
(20 muestras, 4 hits)
o en muchas piezas pequeñas como esta
X...X...X.X..X.........X.....X....X.....
.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^
(20 muestras, 3 hits)
De cualquier manera, el número de hits promediará aproximadamente 1 de 5, sin importar cuántas muestras se tomen, o que pocos (Promedio = 20 * 0.2 = 4. Desviación estándar = +/- sqrt (20 * 0.2 * 0.8) = 1.8.)
que intentas encontrar el cuello de botella
como si solo hubiera 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 que vWxYz
toman 1/2, 1/4, 1/8, 1/16, 1/32 del tiempo, respectivamente. El muestreo se 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, cada vez que se elimina el mayor problema de rendimiento, por porcentaje, hasta que no se encuentre nada que 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 lo que eliminar cualquier problema aumenta el resto, en porcentaje, porque el denominador se reduce.
Otro punto crucial es que se deben encontrar todos los problemas , sin perder ninguno de los 5. Cualquier problema no encontrado y solucionado reduce severamente la relación de aceleración final. Solo encontrar algunos, pero no todos, no es "suficientemente bueno".
AGREGADO: Me gustaría señalar una razón por la cual gprof es popular: se está enseñando, presumiblemente porque es gratis, fácil de enseñar y ha existido durante 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 umich utah utexas utk wustl
** Con la excepción de otras formas de solicitar que se realice el trabajo, eso no deja rastro que indique por qué , como por ejemplo mediante la publicación de mensajes.
Como no vi nada acerca de perf
cuál es una herramienta relativamente nueva para perfilar el núcleo y las aplicaciones de usuario en Linux, decidí agregar esta información.
En primer lugar, este es un tutorial sobre el perfil de Linux conperf
Puede usarlo 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 gprof
requiere). Sin embargo, para obtener el gráfico de llamadas correctamente perf
, necesita 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) Para 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 grabar 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 perfilado 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
Lo uso libmalloc_minimial.so
ya que está compilado -fno-omit-frame-pointer
mientras que libc malloc parece estar compilado sin esta opción. Entonces ejecuto mi programa de prueba
./my_test 100000000
Luego grabo 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, ya sabe dónde pasa el tiempo su 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;
}
f1
estaba llamando delete
. El 40% (aproximadamente) del tiempo process_request
estaba llamando delete
. Una buena parte del resto se gastó en new
. Las medidas son aproximadas, pero los puntos críticos están señalados.
As in my answer, you run it under a debugger and hit ^C at a random time and capture the stack trace
. 1) Creo que su técnica no es útil cuando necesita analizar problemas de rendimiento para un programa que se ejecuta en el servidor de su cliente. 2) No estoy seguro de cómo aplicar esta técnica para obtener información para un programa que tiene muchos hilos que manejan diferentes solicitudes. Quiero decir cuando la imagen general es bastante complicada.
the problem is outside your code
, ¿verdad? Ya que podría necesitar alguna información para respaldar su punto. En esta situación, es posible que en algún momento necesite crear un perfil de su aplicación. No puede simplemente pedirle a su cliente que inicie gdb y presione ^ C y obtenga pilas de llamadas. Este fue mi punto. Este es un ejemplo spielwiese.fontein.de/2012/01/22/… . Tuve este problema y la elaboración de perfiles me ayudó mucho.
Prueba OProfile . Es una herramienta mucho mejor para perfilar su código. También sugeriría Intel VTune .
Las dos herramientas anteriores pueden reducir el tiempo dedicado a una línea de código en particular, anotar su código, mostrar el ensamblaje y la cantidad de instrucción particular que toma. Además de la métrica de tiempo, también puede consultar contadores específicos, es decir, aciertos de caché, etc.
A diferencia de gprof, puede perfilar cualquier proceso / binario que se ejecute en su sistema utilizando cualquiera de los dos.
Las herramientas de rendimiento de Google incluyen un generador de perfiles fácil de usar. La CPU y el generador de perfiles de almacenamiento dinámico están disponibles.
Echa un vistazo a Sysprof .
Su distribución puede tenerlo ya.
http://lttng.org/ si quieres un trazador de alto rendimiento