instalar - programar c++ en linux




¿Cómo puedo perfilar el código C++ que se ejecuta en Linux? (8)

Esta es una respuesta a la respuesta Gprof de Nazgob .

He estado usando Gprof los últimos días y ya he encontrado tres limitaciones importantes, una de las cuales no he visto documentada en ningún otro lugar (todavía):

  1. No funciona correctamente en código de subprocesos múltiples, a menos que utilice una workaround

  2. El gráfico de llamadas se confunde con los punteros de función. Ejemplo: Tengo una función llamada multithread() que me permite multiprocesar una función específica sobre una matriz específica (ambas pasadas como argumentos). Sin embargo, Gprof considera que todas las llamadas a multithread() son equivalentes para calcular el tiempo empleado en niños. Ya que algunas funciones que paso a multithread() llevan mucho más tiempo que otras, mis gráficos de llamadas son en su mayoría inútiles. (Para aquellos que se preguntan si el tema es el subprocesamiento: no, multithread() puede, opcionalmente, y lo hizo en este caso, ejecutar todo secuencialmente solo en el subproceso que llama).

  3. here dice que "... las cifras de la cantidad de llamadas se derivan contando, no muestreando. Son completamente exactas ...". 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 (de 64 bits) en el código e hice la misma ejecución nuevamente. Mis cuentas: 5345859132 directas, y 78094395406 llamadas auto-recursivas. Hay muchos dígitos allí, así que señalaré que las llamadas recursivas que mido son 78 mil millones, en comparación con 784 m de Gprof: un factor de 100 diferentes. Ambas ejecuciones fueron de un solo hilo y código no optimizado, uno compilado -g y el otro -pg .

Esto fue GNU Gprof (GNU Binutils for Debian) 2.18.0.20080103 ejecutándose bajo Debian Lenny de 64 bits, si eso ayuda a alguien.

Tengo una aplicación C ++, que se ejecuta en Linux, que estoy en proceso de optimizar. ¿Cómo puedo identificar qué áreas de mi código se ejecutan lentamente?


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

Para aplicaciones vinculadas a la CPU:

  1. Use un generador de perfiles en modo DEBUG para identificar partes cuestionables de su código
  2. Luego, cambie al modo LIBERAR y comente las secciones cuestionables de su código (apáguelo sin nada) hasta que vea cambios en el rendimiento.

Para aplicaciones de E / S enlazadas:

  1. Use un generador de perfiles en el modo LIBERAR para identificar partes cuestionables de su código.

nótese bien

Si no tienes un perfilador, usa el perfilador del pobre. Pulse pausa mientras se depura su aplicación. La mayoría de las suites de desarrolladores se dividirán en ensamblajes con números de línea comentados. Estadísticamente es 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 generar perfiles en el modo DEBUG es porque si probó la generación de perfiles en el modo LIBERAR , el compilador reducirá los cálculos matemáticos, vectorizados y las funciones en línea que tienden a convertir su código en un desorden no asignable cuando se ensambla. Un desorden no asignable significa que su perfilador no podrá identificar claramente lo que está tomando tanto tiempo porque es posible que el ensamblaje no se corresponda 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 útil.

Para el enlace de E / S, el generador de perfiles todavía puede identificar las operaciones de E / S en el modo LIBERAR 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).


Los kernels más nuevos (por ejemplo, los kernels más recientes de Ubuntu) vienen con las nuevas herramientas 'perf' ( apt-get install linux-tools ) AKA perf_events .

¡Estos vienen con perfiladores de muestreo clásicos ( man-page ) así como con el increíble timechart !

Lo importante es que estas herramientas pueden ser la creación de perfiles del sistema y no solo la generación de perfiles del proceso, sino que pueden mostrar la interacción entre los subprocesos, los procesos y el kernel y permitirle comprender la planificación y las dependencias de E / S entre los procesos.


Para los programas de un solo hilo, puede usar igprof , The Ignominous Profiler: https://igprof.org/ .

Es un perfilador de muestreo, en la línea de ... long ... respuesta por 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


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 está siendo subjetivamente lento, hay una forma sencilla de encontrar problemas de rendimiento.

Solo deténgalo varias veces, y cada vez que mire la pila de llamadas. Si hay algún código que está perdiendo algún porcentaje del tiempo, 20% o 50% o lo que sea, esa es la probabilidad de que lo atrape en el acto en cada muestra. Así que eso es aproximadamente el porcentaje de muestras en las que lo verá. No hay conjeturas educadas requeridas. 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 pases posteriores. Este efecto de aumento , cuando se combina con múltiples problemas, puede llevar a factores de aceleración verdaderamente masivos.

Advertencia: los programadores tienden a ser escépticos de esta técnica a menos que ellos mismos la hayan usado. Dirán que los perfiladores te dan esta información, pero eso solo es cierto si muestrean toda la pila de llamadas y luego te permiten examinar un conjunto aleatorio de muestras. (Los resúmenes son donde se pierde la información). Los gráficos de llamadas no le dan la misma información, porque

  1. no se resumen en el nivel de instrucción, y
  2. Dan resúmenes confusos en presencia de recursión.

También dirán que solo funciona en programas de juguete, 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.

PS Esto también se puede hacer en programas de múltiples subprocesos si hay una forma de recopilar muestras de pila de llamadas del conjunto de subprocesos en un momento dado, como ocurre en Java.

PPS Como generalidad general, cuantas más capas de abstracción tenga en su software, más probabilidades tendrá de descubrir que esa es la causa de los problemas de rendimiento (y la oportunidad de acelerar).

Agregado: puede que no sea obvio, pero la técnica de muestreo en pila funciona igualmente bien en presencia de recursión. La razón es que el tiempo que se ahorraría al eliminar una instrucción se aproxima a la fracción de las muestras que lo 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 se perderá el problema real ". Esto viene de tener un concepto previo de cuál es el problema real. Una propiedad clave de los problemas de rendimiento es que desafían las expectativas. El muestreo le dice que algo es un problema, y ​​su primera reacción es de incredulidad. Eso es natural, pero puede estar seguro de que si encuentra un problema es real y viceversa.

AÑADIDO: Déjame hacer una explicación bayesiana de cómo funciona. Supongamos que hay alguna instrucción I (llamada o de otro tipo) que está en la pila de llamadas alguna fracción f del tiempo (y por lo tanto cuesta tanto). Para simplificar, supongamos que no sabemos qué es f , 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 probablemente a-priori.

Luego, supongamos que tomamos solo 2 muestras apiladas, y vemos la instrucción I en ambas muestras, observación designada o=2/2 . Esto nos da nuevas estimaciones de la frecuencia f de I , de acuerdo 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)

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 que P (f = 0.1) es .991 (casi seguro), y todas las otras posibilidades son casi imposibles (0.001). En otras palabras, nuestra certeza previa es que I barato. 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)

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 previo de 0.6%. Así que Bayes nos permite actualizar nuestra estimación del costo probable de I Si la cantidad de datos es pequeña, no nos dice con exactitud cuál es el costo, solo que es lo suficientemente grande como para que valga la pena arreglarlo.

Otra forma de verlo es la Regla de Sucesión . Si lanzas una moneda 2 veces y sale cara dos veces, ¿qué te dice eso sobre la posible ponderación de la moneda? La forma respetada de responder es decir que es una distribución Beta, con valor promedio (número de aciertos + 1) / (número de intentos + 2) = (2 + 1) / (2 + 2) = 75%.

(La clave es que lo vemos más de una vez. Si solo lo vemos una vez, eso no nos dice mucho, excepto que f > 0).

Por lo tanto, incluso una cantidad muy pequeña de muestras nos puede decir mucho sobre el costo de las instrucciones que ve. (Y los verá con una frecuencia, en promedio, proporcional a su costo. Si se toman n muestras y f es el costo, entonces apareceré en las muestras nf+/-sqrt(nf(1-f)) . Ejemplo , n=10 , f=0.3 , es decir 3+/-1.4 muestras.)

AÑADIDO, para dar una idea intuitiva de la diferencia entre la medición y el muestreo aleatorio de pila:
Ahora hay perfiladores que muestrean la pila, incluso en el reloj de pared, pero lo que sale son las mediciones (o ruta de acceso, o punto caliente, de las que se puede ocultar fácilmente un "cuello de botella"). Lo que no te muestran (y fácilmente podrían) son las muestras reales. 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 30% del tiempo, 2 / .3 = 6.7 muestras, en promedio, lo mostrarán, y la probabilidad de que 20 muestras muestren que es 99.2%.

Aquí hay una ilustración de la diferencia entre examinar las medidas y examinar las muestras de pila. El cuello de botella podría ser una mancha grande como esta, o varias pequeñas, no hace ninguna diferencia.

La medida es horizontal; te dice qué fracción de tiempo llevan las 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 por la que se invierte el tiempo, no solo cuánto.


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

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

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

No lo he probado todavía, pero he oído cosas buenas sobre google-perftools . Definitivamente vale la pena intentarlo.

Pregunta relacionada here .

Algunas otras palabras de moda si gprof no hace el trabajo por usted: Valgrind , Intel VTune , Sun DTrace .


Usaría Valgrind y Callgrind como base para mi conjunto de herramientas de perfiles. Lo que es importante saber es 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 Just-in-time (JIT), incluida la recompilación dinámica. Nada del programa original se ejecuta directamente en el procesador host. En su lugar, Valgrind primero traduce el programa a una forma temporal más simple llamada Representación Intermedia (IR), que es una forma basada en SSA, neutral para el procesador. Después de la conversión, una herramienta (ver a continuación) es libre de hacer las transformaciones que desee en el IR, antes de que Valgrind vuelva a traducir el IR al 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 un resultado confiable. Incluso una segunda ejecución es suficiente para obtener resultados sólidos y confiables, ya que Callgrind es un perfilador sin sondeo .

Otra herramienta construida sobre Valgrind es el macizo. Lo uso para perfilar el uso de la memoria del montón. Funciona muy bien Lo que hace es que le proporciona 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 momentos del tiempo de ejecución de la aplicación.


Utilice Valgrind, callgrind y kcachegrind:

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

Genera callgrind.out.x. Léelo usando kcachegrind.

Utilice gprof (add -pg):

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

(No es tan bueno para múltiples hilos, punteros de función)

Utilice google-perftools:

Utiliza muestreo de tiempo, E / S y cuellos de botella de la CPU se revelan.

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)





profiling