Perfilado en Python: ¿Quién llamó a la función?
profiling (8)
Estoy perfilando en Python usando cProfile
. Encontré una función que requiere mucho tiempo de CPU. ¿Cómo puedo saber qué función llama más a esta función pesada?
EDITAR:
Me conformaré con una solución alternativa: ¿Puedo escribir una línea de Python dentro de esa pesada función que imprimirá el nombre de la función que lo llamó?
Casi siempre veo el resultado del módulo cProfile usando Gprof2dot , básicamente convierte el resultado en un gráfico graphvis (un archivo .dot
), por ejemplo:
Hace que sea muy fácil determinar qué función es más lenta y qué función [s] la llamó.
El uso es:
python -m cProfile -o output.pstats path/to/your/script arg1 arg2
gprof2dot.py -f pstats output.pstats | dot -Tpng -o output.png
Es posible hacerlo usando Profiler cProfile
en la biblioteca estándar.
En pstats.Stats
(el resultado del perfilador) existe el método print_callees
(o alternativamente print_callers
).
Código de ejemplo:
import cProfile, pstats
pr = cProfile.Profile()
pr.enable()
# ... do something ...
pr.disable()
ps = pstats.Stats(pr).strip_dirs().sort_stats(''cumulative'')
ps.print_callees()
El resultado será algo así como:
Function called...
ncalls tottime cumtime
ElementTree.py:1517(_start_list) -> 24093 0.048 0.124 ElementTree.py:1399(start)
46429 0.015 0.041 ElementTree.py:1490(_fixtext)
70522 0.015 0.015 ElementTree.py:1497(_fixname)
ElementTree.py:1527(_data) -> 47827 0.017 0.026 ElementTree.py:1388(data)
47827 0.018 0.053 ElementTree.py:1490(_fixtext)
A la izquierda tienes a quien llama, a la derecha tienes a quien llama.
(por ejemplo _fixtext
se _fixtext
desde _data
47827 veces y desde _start_list
46429 veces)
Ver también:
- docs.python.org/..#print_callees - muestra la jerarquía de llamadas. Agrupe por la persona que llama. (usado arriba)
- docs.python.org/..#print_callers - muestra la jerarquía de llamadas. Agrupar por el destinatario.
Par de notas:
- Su código debe ser editado para esto (inserte esas declaraciones de perfil).
(es decir, no es posible utilizarlo desde la línea de comandos comopython -m cProfile myscript.py
. Aunque es posible escribir un script separado para eso) - Un poco sin relación, pero
strip_dirs()
debe ir antes quesort_stats()
(de lo contrario, la ordenación no funciona)
Es posible que desee echar un vistazo a pycallgraph .
Lamento no estar familiarizado con Python, pero hay un método general que funciona, suponiendo que pueda interrumpir la ejecución manualmente en un momento aleatorio.
Solo hazlo y visualiza la pila de llamadas. Te dirá, con alta probabilidad, lo que quieres saber. Si quieres estar más seguro, hazlo varias veces.
Funciona porque la persona que llama culpable tiene que estar en la pila de llamadas durante la fracción de tiempo que se desperdicia, lo que la expone a las interrupciones durante la mayor parte del tiempo, ya sea que se propague a través de llamadas cortas o largas.
NOTA: Este proceso se parece más al diagnóstico que a la medición. Supongamos que una mala llamada está perdiendo el 90% del tiempo. Luego, cada vez que la detenga, la probabilidad es del 90% de que la declaración de llamada incorrecta esté allí en la pila de llamadas para que usted la vea, y podrá ver que es mala. Sin embargo, si quiere medir exactamente el desperdicio, ese es un problema diferente. Para eso, necesitará muchas más muestras, para ver qué porcentaje contiene esa llamada. O, como alternativa, solo arregla la llamada culpable, registra la aceleración, y eso te dirá exactamente qué fue el desperdicio.
No he usado cProfile, pero la mayoría de los profilers te dan una jerarquía de llamadas.
Google buscando esta slides sobre cProfile. Tal vez eso ayude. Parece que cProfile proporciona una jerarquía.
Puede que no responda tu pregunta directamente, pero definitivamente ayudará. Si usa el generador de perfiles con la opción - ordenado acumulativo, ordenará las funciones por tiempo acumulado. Lo cual es útil para detectar no solo las funciones pesadas sino también las funciones que las llaman.
python -m cProfile --sort cumulative myScript.py
Hay una solución para obtener la función de llamada:
import inspect
print inspect.getframeinfo(inspect.currentframe().f_back)[2]
Puede agregar tantos f_back como desee en caso de que quiera llamar al llamante, etc. Si desea calcular llamadas frecuentes, puede hacer esto:
record = {}
caller = inspect.getframeinfo(inspect.currentframe().f_back)[2]
record[caller] = record.get(caller, 0) + 1
Luego imprímalos por orden de frecuencia:
print sorted(record.items(), key=lambda a: a[1])
Pycscope hace esto. Lo acabo de encontrar hoy, así que no puedo decir lo bueno que es, pero los pocos ejemplos que he probado han sido bastante buenos (aunque no perfectos).
https://pypi.python.org/pypi/pycscope/
Usted usaría esto para generar un archivo cscope y luego un plugin cscope de un editor, específicamente VIM. Intenté usarlo con cscope de vainilla, parece que el cscope simple se confunde.
inspect.stack() le dará la pila de llamadas actual.