GNU/Linux >> Tutoriales Linux >  >> Linux

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

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 mire la pila de llamadas. Si hay algún código que está desperdiciando 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. Entonces, ese es aproximadamente el porcentaje de muestras en las que lo verá. No se requieren conjeturas educadas. Si tiene una idea de cuál es el problema, esto lo probará o lo refutara.

Es posible que tenga 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 las pasadas 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 generadores de perfiles le brindan esta información, pero eso solo es cierto si muestrean toda la pila de llamadas y luego le permiten examinar un conjunto aleatorio de muestras. (Los resúmenes es donde se pierde la información). Los gráficos de llamadas no le brindan la misma información, porque

  1. No resumen a nivel de instrucción, y
  2. Dan resúmenes confusos en presencia de recursividad.

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.

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

PPS Como generalidad aproximada, cuantas más capas de abstracción tenga su software, más probable es que descubra que esa es la causa de los problemas de rendimiento (y la oportunidad de acelerar).

Añadido :Puede que no sea obvio, pero la técnica de muestreo de pila funciona igual de bien en presencia de recursividad. La razón es que el tiempo que se ahorraría al eliminar una instrucción se aproxima a 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 no detectará el verdadero problema ".Esto proviene de tener un concepto previo de cuál es el problema real. Una propiedad clave de los problemas de desempeño es que desafían las expectativas. El muestreo le dice que algo es un problema, y ​​su primera reacción es incredulidad. Eso es natural, pero puede asegúrese de que si encuentra un problema, es real y viceversa.

Añadido :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 alguna fracción f del tiempo (y por lo tanto cuesta tanto). Para simplificar, supongamos que no sabemos qué f es, pero suponga 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.

Entonces supongamos que tomamos solo 2 muestras de pila 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 , según 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 es 92 %, por encima de la suposición anterior de 60 %.

Supongamos que las suposiciones anteriores son diferentes. Supongamos que asumimos P(f=0.1) es .991 (casi seguro), y todas las demás posibilidades son casi imposibles (0.001). En otras palabras, nuestra certeza previa es que I es 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 | 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 P(f >= 0.5) es del 26%, por encima del supuesto anterior del 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 repararla.

Otra forma más de verlo se llama la regla de sucesión. Si lanzas una moneda 2 veces y sale cara las dos veces, ¿qué te dice eso sobre el peso probable de la moneda? La forma respetada de responder es digamos que es una distribución Beta, con valor promedio (number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75% .

(La clave es que vemos I mas 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 puede decirnos mucho sobre el costo de las instrucciones que ve. (Y los verá con una frecuencia, en promedio, proporcional a su costo. Si n se toman muestras y f es el costo, entonces I aparecerá en nf+/-sqrt(nf(1-f)) muestras Ejemplo, n=10 , f=0.3 , eso es 3+/-1.4 muestras.)

Añadido :Para dar una idea intuitiva de la diferencia entre la medición y el muestreo aleatorio de pilas:
Ahora hay generadores de perfiles que prueban la pila, incluso a la hora del reloj de pared, pero lo que sale son medidas (o ruta activa, o punto activo, desde el cual se puede ocultar fácilmente un "cuello de botella"). Lo que no le muestran (y fácilmente podrían hacerlo) son las muestras reales. Y si tu 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 tarda. Entonces, si tarda el 30 % del tiempo, 2/0,3 =6,7 muestras, en promedio, lo mostrarán, y la probabilidad de que 20 muestras lo muestren es del 99,2 %.

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

La medida es horizontal; le dice qué fracción de tiempo toman 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 el motivo completo del tiempo que se gasta, no solo cuánto.


Puede usar Valgrind con las siguientes opciones

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

Generará un archivo llamado callgrind.out.x . A continuación, puede utilizar kcachegrind herramienta para leer este archivo. Le dará un análisis gráfico de las cosas con resultados como qué líneas cuestan 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

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


Linux
  1. Cómo matar procesos en ejecución en Linux

  2. Cómo llamar a la función C en C++, función C++ en C (Mezclar C y C++)

  3. ¿Cómo puedo crear un árbol de directorios en C++/Linux?

  4. ¿Cómo puedo vincular un archivo en Linux?

  5. ¿Cómo matar un proceso que se ejecuta en un puerto particular en Linux?

Cómo enumerar los procesos en ejecución en Linux

Cómo compilar y ejecutar programas C, C++ en Linux

Cómo matar el proceso de ejecución de Linux en un puerto particular

¿Cómo puedo generar cobertura de código para paquetes Swift en Linux u OS X?

¿Cómo puedo crear un archivo de volcado de un proceso en ejecución en Linux?

¿Cómo puedo buscar un nombre de usuario por id en Linux?