Alternativas a gprof [cerrado]


166

¿Qué otros programas hacen lo mismo que gprof?


2
¿Qué plataformas te interesan?
osgx

2
Estoy interesado en Linux.
neuromancer


13
@Gregory: me inclino a estar de acuerdo, y tal vez debería contribuir con sus propias respuestas, 229 frente a 6, y las 6 respuestas corresponden a sus propias preguntas ...
Jean-Bernard Pellerin,

55
¿Cómo puede esta pregunta no ser constructiva?
JohnTortugo

Respuestas:


73

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.


2
@ Norman: ++ Esa confusión sobre la recursividad parece endémica en los sistemas que tienen el concepto de tiempos de propagación entre los nodos en un gráfico. También creo que el tiempo del reloj de pared es generalmente más útil que los tiempos de instrucción de la CPU, y las líneas de código (instrucciones de llamada) son más útiles que los procedimientos. Si se toman muestras apiladas en tiempos de reloj de pared aleatorios, entonces el costo fraccional de una línea (o procedimiento, o cualquier otra descripción que pueda hacer) simplemente se estima por la fracción de muestras que la exhiben.
Mike Dunlavey

1
... Estoy haciendo hincapié en las instrucciones de llamada, pero se aplica a cualquier instrucción. Si uno tiene un cuello de botella hotspot honesto a bondad, como un tipo de burbuja de una gran variedad de números, entonces las instrucciones de comparación / salto / intercambio / incremento del bucle interno estarán en la parte superior / inferior de casi cada muestra de pila . Pero (especialmente a medida que el software crece y casi ninguna rutina tiene mucho tiempo "propio"), muchos problemas en realidad son instrucciones de llamada, que requieren trabajo que, cuando está claro cuánto cuesta, no tiene que hacerse realmente .
Mike Dunlavey

3
... Mira esto. Creo que están casi en el camino correcto: rotateright.com/zoom.html
Mike Dunlavey

195

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 callinstrucciones), 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:

  1. 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.

  2. 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).

  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 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.

  4. 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%.

  5. 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.

  6. 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.

  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 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.

  8. 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.

  9. 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.)

  10. 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 vWxYztoman 1/2, 1/4, 1/8, 1/16, 1/32 del tiempo, respectivamente. El muestreo se encuentra vfácilmente. Se elimina, dejando.
    xWzWxWYWxW.WxWYW
    Ahora el programa tarda la mitad de tiempo en ejecutarse, y ahora Wtarda 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.


3
@ Norman: Hice un perfilador basado en esto, en C para DOS, alrededor del '93. Lo llamé otro analizador de rendimiento y lo demostré en las reuniones de IEEE, pero eso fue todo. Hay un producto de RotateRight llamado Zoom que no está muy lejos. En * nix, pstack es bueno para hacerlo manualmente. Mi lista de tareas pendientes para el trabajo (farmacometría en Windows) tiene aproximadamente una milla de largo, lo que impide proyectos divertidos, sin mencionar a la familia. Esto podría ser útil: stackoverflow.com/questions/1777669/…
Mike Dunlavey

66
Siempre he encontrado que los perfiladores no son tan útiles para corregir el código lento, y en su lugar usé bits selectivos de código de depuración para medir el tiempo que toma un grupo de declaraciones de mi elección, a menudo ayudado por algunas pequeñas macros triviales o lo que sea. Nunca me ha llevado demasiado tiempo encontrar al culpable, pero siempre me ha avergonzado mi enfoque de "pieles de oso y cuchillos de piedra" cuando "todos los demás" (por lo que sé) usan las herramientas sofisticadas. Gracias por mostrarme por qué nunca pude obtener la información que necesitaba de profiler. Esta es una de las ideas más importantes que he visto en SO. ¡Bien hecho!
Wayne Conrad

77
@osgx: no quiero rasgar nada. Es como un viejo automóvil favorito, simple y resistente, pero hay cosas que no hace, y debemos ser conscientes de eso, y no solo eso, debemos despertar de los mitos. Aprecio que en algunas plataformas puede ser difícil obtener muestras de la pila, pero si un problema es tal que gprof no lo encontrará, el hecho de que sea la única herramienta es una pequeña comodidad.
Mike Dunlavey

2
@ Andrew: ... y si esa razón se aplica a una fracción significativa de muestras (como más de 1), entonces las líneas de código que podrían eliminar esa actividad están en esas muestras. Un gráfico puede darle una pista de esto, pero un número no grande de muestras de pila simplemente se las mostrará.
Mike Dunlavey

2
@Matt: ejemplos de problemas de rendimiento de E / S encontrados de esta manera: 1) imprimir mensajes de registro en un archivo o la consola, que erróneamente se consideró insignificante. 2) Conversión entre texto y dobles en IO numérico. 3) IO subterráneo que extrae cadenas internacionalizadas durante el inicio, las cadenas que resulta que no necesitaban internacionalizarse. He encontrado muchos ejemplos como estos.
Mike Dunlavey

63

Como no vi nada acerca de perfcuá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 perfsi su kernel de Linux es mayor que 2.6.32 o oprofilesi es anterior. Ambos programas no requieren que usted instrumente su programa (como gprofrequiere). 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.soya que está compilado -fno-omit-frame-pointermientras 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;
}

Acabo de dar su ejemplo y tomé 5 stackshots. Esto es lo que encontraron: el 40% (aproximadamente) del tiempo f1estaba llamando delete. El 40% (aproximadamente) del tiempo process_requestestaba llamando delete. Una buena parte del resto se gastó en new. Las medidas son aproximadas, pero los puntos críticos están señalados.
Mike Dunlavey

¿Qué es un stackshot? ¿Es eso lo que pstacksale?

2
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.

2
En cuanto al # 1. A veces los clientes llaman y dicen que su programa funciona lentamente. No puedes decir eso inmediatamente 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.

2
En cuanto al # 2. Simplificar es un buen enfoque, estoy de acuerdo. A veces funciona Si se produce un problema de rendimiento solo en el servidor de un cliente y no puede reproducirlo en su servidor, entonces los perfiles son útiles.

21

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.


2
Como también se menciona en la respuesta valgrind, Zoom from RotateRight ( rotateright.com ) proporciona una interfaz mucho mejor y permite la creación de perfiles remota.
JanePhanie

no me gustó el oprofile, parecía al azar
Matt Joiner

@ Matt algún punto en particular?
Anycorn

No pudo hacer frente a más de 10 segundos de ejecución antes de generar desbordamientos de estadísticas, el resultado no fue particularmente útil y la documentación es terrible.
Matt Joiner

1
@Tho OProfile: ARM, POWER, ia64, ...
Anycorn



Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.