tutorial tool c++ linux performance profiling perf

c++ - tool - linux perf tutorial



Linux perf: cómo interpretar y encontrar hotspots (5)

Ok, estas funciones pueden ser lentas, pero ¿cómo puedo saber de dónde las llaman? Como todos estos hotspots se encuentran en bibliotecas externas, no veo forma de optimizar mi código.

¿Está seguro de que su aplicación someapp está construida con la opción gcc -fno-omit-frame-pointer (y posiblemente sus bibliotecas dependientes)? Algo como esto:

g++ -m64 -fno-omit-frame-pointer -g main.cpp

Hoy probé la utilidad perf linux y tengo problemas para interpretar sus resultados. Estoy acostumbrado a hacer callgrind con Valgrind, que es, por supuesto, un enfoque totalmente diferente al método de muestreo basado en muestreo.

Lo que hice:

perf record -g -p $(pidof someapp) perf report -g -n

Ahora veo algo como esto:

+ 16.92% kdevelop libsqlite3.so.0.8.6 [.] 0x3fe57 ↑ + 10.61% kdevelop libQtGui.so.4.7.3 [.] 0x81e344 ▮ + 7.09% kdevelop libc-2.14.so [.] 0x85804 ▒ + 4.96% kdevelop libQtGui.so.4.7.3 [.] 0x265b69 ▒ + 3.50% kdevelop libQtCore.so.4.7.3 [.] 0x18608d ▒ + 2.68% kdevelop libc-2.14.so [.] memcpy ▒ + 1.15% kdevelop [kernel.kallsyms] [k] copy_user_generic_string ▒ + 0.90% kdevelop libQtGui.so.4.7.3 [.] QTransform::translate(double, double) ▒ + 0.88% kdevelop libc-2.14.so [.] __libc_malloc ▒ + 0.85% kdevelop libc-2.14.so [.] memcpy ...

Ok, estas funciones pueden ser lentas, pero ¿cómo puedo saber de dónde las llaman? Como todos estos hotspots se encuentran en bibliotecas externas, no veo forma de optimizar mi código.

Básicamente estoy buscando algún tipo de gráfico de llamadas anotado con costo acumulado, donde mis funciones tienen un costo de muestreo más alto que las funciones de biblioteca a las que llamo.

¿Es esto posible con perf? ¿Si es así, cómo?

Nota: Descubrí que "E" desenvuelve el gráfico de llamadas y brinda algo más de información. Pero el gráfico de llamadas a menudo no es lo suficientemente profundo y / o termina de manera aleatoria sin proporcionar información sobre la cantidad de información que se gastó en dónde. Ejemplo:

- 10.26% kate libkatepartinterfaces.so.4.6.0 [.] Kate::TextLoader::readLine(int&... Kate::TextLoader::readLine(int&, int&) Kate::TextBuffer::load(QString const&, bool&, bool&) KateBuffer::openFile(QString const&) KateDocument::openFile() 0x7fe37a81121c

¿Podría ser un problema que estoy ejecutando en 64 bits? Ver también: http://lists.fedoraproject.org/pipermail/devel/2010-November/144952.html (no estoy usando fedora pero parece aplicarse a todos los sistemas de 64 bits).


A menos que su programa tenga muy pocas funciones y casi nunca llame a una función del sistema o de E / S, los perfiladores que muestrean el contador del programa no le dirán mucho, como está descubriendo. De hecho, el conocido generador de perfiles gprof se creó específicamente para tratar de solucionar la inutilidad del perfilado de tiempo libre (no es que haya tenido éxito).

Lo que realmente funciona es algo que toma muestras de la pila de llamadas (por lo tanto, descubre de dónde provienen las llamadas), en el reloj de pared (lo que incluye el tiempo de E / S), e informa por línea o por instrucción (por lo tanto, señala las funciones de las llamadas que Debes investigar, no solo las funciones en las que viven).

Además, la estadística que debe buscar es el porcentaje de tiempo en la pila , no el número de llamadas, no el tiempo promedio de la función inclusiva. Especialmente no "self time". Si una instrucción de llamada (o una instrucción que no es de llamada) está en la pila el 38% del tiempo, entonces si pudiera deshacerse de ella, ¿cuánto ahorraría? 38%! Bastante simple, ¿no?

Un ejemplo de un perfilador de este tipo es Zoom .

Hay más cuestiones que entender sobre este tema.

Agregado: @caf me hizo buscar la información de perf , y como incluyó el argumento de la línea de comandos -g , sí recopila muestras de pila. Entonces puede obtener un informe de call-tree . Luego, si se asegura de que esté tomando muestras en el reloj de pared (para que obtenga el tiempo de espera y el tiempo de la CPU), tendrá casi lo que necesita.


Con Linux 3.7 perf es finalmente capaz de usar la información DWARF para generar el gráfico de llamadas:

perf record --call-graph dwarf -- yourapp perf report -g graph --no-children

Limpio, pero la GUI de cursas es horrible en comparación con VTune, KCacheGrind o similar ... Recomiendo probar FlameGraphs en su lugar, que es una visualización bastante clara: http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html

Nota: En el paso del informe, el -g graph hace que la salida de los resultados sea fácil de entender, en lugar de los números "relativos al padre", en los resultados. --no-children mostrarán solo el costo propio, en lugar del costo inclusivo, una característica que también encuentro invaluable.

Si tiene un nuevo procesador y una CPU Intel, también pruebe el desenrollador LBR, que tiene un rendimiento mucho mejor y produce archivos de resultados mucho más pequeños:

perf record --call-graph lbr -- yourapp

La desventaja aquí es que la profundidad de la pila de llamadas es más limitada en comparación con la configuración predeterminada de desbobinador DWARF.



Puede obtener un informe muy detallado a nivel de fuente con perf annotate , consulte Análisis de nivel de fuente con anotación de rendimiento . Se verá algo así (robado descaradamente del sitio web):

------------------------------------------------ Percent | Source code & Disassembly of noploop ------------------------------------------------ : : : : Disassembly of section .text: : : 08048484 <main>: : #include <string.h> : #include <unistd.h> : #include <sys/time.h> : : int main(int argc, char **argv) : { 0.00 : 8048484: 55 push %ebp 0.00 : 8048485: 89 e5 mov %esp,%ebp [...] 0.00 : 8048530: eb 0b jmp 804853d <main+0xb9> : count++; 14.22 : 8048532: 8b 44 24 2c mov 0x2c(%esp),%eax 0.00 : 8048536: 83 c0 01 add $0x1,%eax 14.78 : 8048539: 89 44 24 2c mov %eax,0x2c(%esp) : memcpy(&tv_end, &tv_now, sizeof(tv_now)); : tv_end.tv_sec += strtol(argv[1], NULL, 10); : while (tv_now.tv_sec < tv_end.tv_sec || : tv_now.tv_usec < tv_end.tv_usec) { : count = 0; : while (count < 100000000UL) 14.78 : 804853d: 8b 44 24 2c mov 0x2c(%esp),%eax 56.23 : 8048541: 3d ff e0 f5 05 cmp $0x5f5e0ff,%eax 0.00 : 8048546: 76 ea jbe 8048532 <main+0xae> [...]

No olvide pasar los -fno-omit-frame-pointer y -ggdb cuando compile su código.