¿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?

Gabriel Isenberg
fuente
27
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?
Nazgob
2
Ya está respondido en el siguiente enlace: stackoverflow.com/questions/2497211/…
Kapil Gupta
44
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.
ruido sin
3
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

  1. No resumen en el nivel de instrucción, y
  2. 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.1    1     1             0.1          0.1            0.25974026
0.1    0.9   0.81          0.081        0.181          0.47012987
0.1    0.8   0.64          0.064        0.245          0.636363636
0.1    0.7   0.49          0.049        0.294          0.763636364
0.1    0.6   0.36          0.036        0.33           0.857142857
0.1    0.5   0.25          0.025        0.355          0.922077922
0.1    0.4   0.16          0.016        0.371          0.963636364
0.1    0.3   0.09          0.009        0.38           0.987012987
0.1    0.2   0.04          0.004        0.384          0.997402597
0.1    0.1   0.01          0.001        0.385          1

                  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.001  1    1              0.001        0.001          0.072727273
0.001  0.9  0.81           0.00081      0.00181        0.131636364
0.001  0.8  0.64           0.00064      0.00245        0.178181818
0.001  0.7  0.49           0.00049      0.00294        0.213818182
0.001  0.6  0.36           0.00036      0.0033         0.24
0.001  0.5  0.25           0.00025      0.00355        0.258181818
0.001  0.4  0.16           0.00016      0.00371        0.269818182
0.001  0.3  0.09           0.00009      0.0038         0.276363636
0.001  0.2  0.04           0.00004      0.00384        0.279272727
0.991  0.1  0.01           0.00991      0.01375        1

                  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.

ingrese la descripción de la imagen aquí

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.

Mike Dunlavey
fuente
292
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.
Sam Harwell
583

Puedes usar Valgrind con las siguientes opciones

valgrind --tool=callgrind ./(Your binary)

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.

Ajay
fuente
51
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.
enthusiasticgeek
1
stackoverflow.com/questions/375913/… es una solución parcial para problemas de velocidad.
Tõnu Samuel
3
@Sebastian: gprof2dotahora está aquí: github.com/jrfonseca/gprof2dot
John Zwinck
348

Supongo que estás usando GCC. La solución estándar sería perfilar con gprof .

Asegúrese de agregar -pga la compilación antes de perfilar:

cc -o myprog myprog.c utils.c -g -pg

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 gprofno hacen el trabajo por usted: Valgrind , Intel VTune , Sun DTrace .

Nazgob
fuente
3
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
dario minonne
77
@ Bill-the-Lizard: Algunos comentarios sobre gprof : stackoverflow.com/questions/1777556/alternatives-to-gprof/…
Mike Dunlavey
66
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 .

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

texto alternativo

Será
fuente
12
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!
guiones
2
Incluso los núcleos más nuevos como 4.13 tienen eBPF para la creación de perfiles. Ver brendangregg.com/blog/2015-05-15/ebpf-one-small-step.html y brendangregg.com/ebpf.html
Andrew Stern el
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.


fuente
70

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:

valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp

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.

Tõnu Samuel
fuente
99
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
Andre Holzner
1
Wow, no sabía que esto existía, ¡gracias!
Vincent Fourmond el
59

Esta 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):

  1. No funciona correctamente en código multiproceso, a menos que use una solución alternativa

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

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

Rob_before_edits
fuente
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)

Ehsan
fuente
11

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:

  • 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):

gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 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:

gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000

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.

Podemos observar ese archivo gráficamente con gprof2dotlo solicitado en: ¿Es posible obtener una representación gráfica de los resultados de gprof?

sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg

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 fuente de gprof2dot está en: https://github.com/jrfonseca/gprof2dot

Observamos lo siguiente para la -O0carrera:

ingrese la descripción de la imagen aquí

y para la -O3carrera:

ingrese la descripción de la imagen aquí

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 -O3 maines 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 :

ingrese la descripción de la imagen aquí

¿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.35      3.67     3.67   123003     0.00     0.00  common
  0.00      3.67     0.00        3     0.00     0.03  fast
  0.00      3.67     0.00        3     0.00     1.19  maybe_slow

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.27% of 3.67 seconds

index % time    self  children    called     name
                0.09    0.00    3003/123003      fast [4]
                3.58    0.00  120000/123003      maybe_slow [3]
[1]    100.0    3.67    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]    100.0    0.00    3.67                 main [2]
                0.00    3.58       3/3           maybe_slow [3]
                0.00    0.09       3/3           fast [4]
-----------------------------------------------
                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/123003      common [1]
-----------------------------------------------
                0.00    0.09       3/3           main [2]
[4]      2.4    0.00    0.09       3         fast [4]
                0.09    0.00    3003/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.52      1.84     1.84   123003    14.96    14.96  common

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.54% of 1.84 seconds

index % time    self  children    called     name
                0.04    0.00    3003/123003      fast [3]
                1.79    0.00  120000/123003      maybe_slow [2]
[1]    100.0    1.84    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]     97.6    0.00    1.79                 maybe_slow [2]
                1.79    0.00  120000/123003      common [1]
-----------------------------------------------
                                                 <spontaneous>
[3]      2.4    0.00    0.04                 fast [3]
                0.04    0.00    3003/123003      common [1]
-----------------------------------------------

Index by function name

   [1] common

Como un resumen muy rápido para cada sección, por ejemplo:

                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/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.

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

Entonces compilamos y ejecutamos como:

sudo apt install kcachegrind valgrind
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time valgrind --tool=callgrind valgrind --dump-instr=yes \
  --collect-jumps=yes ./main.out 10000

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:

ingrese la descripción de la imagen aquí

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

ingrese la descripción de la imagen aquí

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:

Samples: 7K of event 'cycles:uppp', Event count (approx.): 6228527608     
  Children      Self  Command   Shared Object     Symbol                  
-   99.98%    99.88%  main.out  main.out          [.] common              
     common                                                               
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.01%     0.01%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.01%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.01%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.01%     0.00%  main.out  ld-2.27.so        [.] mprotect            
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.01%     0.00%  main.out  ld-2.27.so        [.] _xstat              
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x2f3d4f4944555453  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007fff3cfc57ac  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              

Entonces trato de comparar el -O0programa para ver si eso muestra algo, y solo ahora, por fin, veo un gráfico de llamadas:

Samples: 15K of event 'cycles:uppp', Event count (approx.): 12438962281   
  Children      Self  Command   Shared Object     Symbol                  
+   99.99%     0.00%  main.out  [unknown]         [.] 0x04be258d4c544155  
+   99.99%     0.00%  main.out  libc-2.27.so      [.] __libc_start_main   
-   99.99%     0.00%  main.out  main.out          [.] main                
   - main                                                                 
      - 97.54% maybe_slow                                                 
           common                                                         
      - 2.45% fast                                                        
           common                                                         
+   99.96%    99.85%  main.out  main.out          [.] common              
+   97.54%     0.03%  main.out  main.out          [.] maybe_slow          
+    2.45%     0.00%  main.out  main.out          [.] fast                
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.00%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.00%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_lookup_symbol_x 
     0.00%     0.00%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.00%     0.00%  main.out  ld-2.27.so        [.] mmap64              
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x552e53555f6e653d  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007ffe1cf20fdb  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              

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.

Una cosa genial perfes 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í perfsin sudolo conseguí ERROR: No stack counts foundpor ahora lo haré con sudo:

git clone https://github.com/brendangregg/FlameGraph
sudo perf record -F 99 -g -o perf_with_stack.data ./main.out 10000
sudo perf script -i perf_with_stack.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

pero en un programa tan simple, la salida no es muy fácil de entender, ya que no podemos ver fácilmente maybe_slowni fasten ese gráfico:

ingrese la descripción de la imagen aquí

En un ejemplo más complejo queda claro lo que significa el gráfico:

ingrese la descripción de la imagen aquí

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 lo perfsuficientemente nuevo, cualquiera de los cuales no es el caso para el rendimiento en Ubuntu 18.04

  • https://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:

sudo apt install google-perftools

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:

gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libprofiler.so \
  CPUPROFILE=prof.out ./main.out 10000

Alternativamente, podemos construir la biblioteca en el momento del enlace, distribuyendo el paso LD_PRELOADen tiempo de ejecución:

gcc -Wl,--no-as-needed,-lprofiler,--as-needed -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
CPUPROFILE=prof.out ./main.out 10000

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:

google-pprof --callgrind main.out prof.out  > callgrind.out
kcachegrind callgrind.out

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

ingrese la descripción de la imagen aquí

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
     187 100.0% 100.0%      187 100.0% common
       0   0.0% 100.0%      187 100.0% __libc_start_main
       0   0.0% 100.0%      187 100.0% _start
       0   0.0% 100.0%        4   2.1% fast
       0   0.0% 100.0%      187 100.0% main
       0   0.0% 100.0%      183  97.9% maybe_slow

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.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
2
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.

fwyzard
fuente
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

  1. 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
  2. Intel VTune ( https://software.intel.com/en-us/vtune ): si tiene compiladores de inteligencia, esto es muy bueno
  3. TAU ( http://www.cs.uoregon.edu/research/tau/home.php )

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.

raovgarimella
fuente
4

Estos son los dos métodos que uso para acelerar mi código:

Para aplicaciones vinculadas a la CPU:

  1. Use un generador de perfiles en modo DEPURAR para identificar partes cuestionables de su código
  2. 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:

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

seo
fuente
2
+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ó.
seo
2

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:

BullyWiiPlaza
fuente
1

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:

// EVENT_NAME                         ID      BEGIN_END BG_COLOR NAME
#define SOCK_PDU_RECV_D               0x0301  //@D00301 BGEEAAAA # TX_PDU_Recv
#define SOCK_PDU_RECV_F               0x0302  //@F00301 BGEEAAAA # TX_PDU_Recv

También define algunas funciones en toolname.hpp:

#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARN 1
// ...

void init(void);
void probe(id,payload);
// etc

En cualquier parte de su código puede usar:

toolname<LOG_LEVEL>::log(EVENT_NAME,VALUE);

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.

Soks
fuente
1

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

  1. ¿Es correcto mi algoritmo?
  2. ¿Hay cerraduras que están demostrando ser cuellos de botella?
  3. ¿hay una sección específica de código que demuestre ser la culpable?
  4. ¿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.

u__
fuente
1

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.

Para obtener más información, puede consultar https://sourceware.org/binutils/docs-2.32/gprof/

Mehdi_Pejvak
fuente
0

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.

Wei
fuente
0

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.

creo que sí


fuente