Como posso criar o perfil do código Python linha por linha?


116

Tenho usado o cProfile para criar o perfil do meu código e está funcionando muito bem. Também uso gprof2dot.py para visualizar os resultados (torna-o um pouco mais claro).

No entanto, cProfile (e a maioria dos outros criadores de perfil Python que vi até agora) parecem ter perfil apenas no nível de chamada de função. Isso causa confusão quando certas funções são chamadas de lugares diferentes - não tenho ideia se a chamada 1 ou 2 está ocupando a maior parte do tempo. Isso fica ainda pior quando a função em questão tem seis níveis de profundidade, chamada de sete outros lugares.

Como faço para obter um perfil linha por linha?

Em vez disso:

function #12, total time: 2.0s

Eu gostaria de ver algo assim:

function #12 (called from somefile.py:102) 0.5s
function #12 (called from main.py:12) 1.5s

cProfile mostra quanto do tempo total "transfere" para o pai, mas novamente essa conexão é perdida quando você tem um monte de camadas e chamadas interconectadas.

Idealmente, adoraria ter uma GUI que analisasse os dados e, em seguida, mostrasse meu arquivo de origem com um tempo total atribuído a cada linha. Algo assim:

main.py:

a = 1 # 0.0s
result = func(a) # 0.4s
c = 1000 # 0.0s
result = func(c) # 5.0s

Então, eu seria capaz de clicar na segunda chamada "func (c)" para ver o que está demorando nessa chamada, separada da chamada "func (a)".

Isso faz sentido? Existe alguma biblioteca de perfis que coleta esse tipo de informação? Existe alguma ferramenta incrível que eu perdi?


2
Meu palpite é que você estaria interessado pstats.print_callers. Um exemplo está aqui .
Muhammad Alkarouri,

Muhammad, isso é definitivamente útil! Pelo menos corrige um problema: separar chamadas de função dependendo da origem. Acho que a resposta de Joe Kington está mais perto do meu objetivo, mas print_callers () definitivamente me leva a metade do caminho. Obrigado!
rocketmonkeys

Respostas:


120

Eu acredito que é para isso que o line_profiler de Robert Kern se destina. Do link:

File: pystone.py
Function: Proc2 at line 149
Total time: 0.606656 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   149                                           @profile
   150                                           def Proc2(IntParIO):
   151     50000        82003      1.6     13.5      IntLoc = IntParIO + 10
   152     50000        63162      1.3     10.4      while 1:
   153     50000        69065      1.4     11.4          if Char1Glob == 'A':
   154     50000        66354      1.3     10.9              IntLoc = IntLoc - 1
   155     50000        67263      1.3     11.1              IntParIO = IntLoc - IntGlob
   156     50000        65494      1.3     10.8              EnumLoc = Ident1
   157     50000        68001      1.4     11.2          if EnumLoc == Ident1:
   158     50000        63739      1.3     10.5              break
   159     50000        61575      1.2     10.1      return IntParIO

Espero que ajude!


10
O line_profiler funciona com Python 3? Não consegui obter nenhuma informação sobre isso.
user1251007

3
line_profiler não mostra hits e tempo para mim. Alguém pode me dizer o porquê? E como resolver?
I159

6
Aqui está o decorador que escrevi: gist.github.com/kylegibson/6583590 . Se você estiver executando nosetests, certifique-se de usar a opção -s para que stdout seja impresso imediatamente.
Kyle Gibson

5
como é o script python que produz essa saída? import line_profiler;e depois ?
Zhubarb

10
alguém pode mostrar como realmente usar esta biblioteca? O leia-me ensina como instalar e responde a várias perguntas frequentes, mas não menciona como usá-lo após a instalação do pip ..
cryanbhu

47

Você também pode usar pprofile ( pypi ). Se você deseja traçar o perfil de toda a execução, não requer modificação do código-fonte. Você também pode criar o perfil de um subconjunto de um programa maior de duas maneiras:

  • alterne a criação de perfil ao atingir um ponto específico no código, como:

    import pprofile
    profiler = pprofile.Profile()
    with profiler:
        some_code
    # Process profile content: generate a cachegrind file and send it to user.
    
    # You can also write the result to the console:
    profiler.print_stats()
    
    # Or to a file:
    profiler.dump_stats("/tmp/profiler_stats.txt")
  • alternar a criação de perfil de forma assíncrona da pilha de chamadas (requer uma maneira de acionar este código em um aplicativo considerado, por exemplo, um manipulador de sinal ou um thread de trabalho disponível) usando a criação de perfil estatística:

    import pprofile
    profiler = pprofile.StatisticalProfile()
    statistical_profiler_thread = pprofile.StatisticalThread(
        profiler=profiler,
    )
    with statistical_profiler_thread:
        sleep(n)
    # Likewise, process profile content

O formato de saída da anotação de código é muito parecido com o gerador de perfil de linha:

$ pprofile --threads 0 demo/threads.py
Command line: ['demo/threads.py']
Total duration: 1.00573s
File: demo/threads.py
File duration: 1.00168s (99.60%)
Line #|      Hits|         Time| Time per hit|      %|Source code
------+----------+-------------+-------------+-------+-----------
     1|         2|  3.21865e-05|  1.60933e-05|  0.00%|import threading
     2|         1|  5.96046e-06|  5.96046e-06|  0.00%|import time
     3|         0|            0|            0|  0.00%|
     4|         2|   1.5974e-05|  7.98702e-06|  0.00%|def func():
     5|         1|      1.00111|      1.00111| 99.54%|  time.sleep(1)
     6|         0|            0|            0|  0.00%|
     7|         2|  2.00272e-05|  1.00136e-05|  0.00%|def func2():
     8|         1|  1.69277e-05|  1.69277e-05|  0.00%|  pass
     9|         0|            0|            0|  0.00%|
    10|         1|  1.81198e-05|  1.81198e-05|  0.00%|t1 = threading.Thread(target=func)
(call)|         1|  0.000610828|  0.000610828|  0.06%|# /usr/lib/python2.7/threading.py:436 __init__
    11|         1|  1.52588e-05|  1.52588e-05|  0.00%|t2 = threading.Thread(target=func)
(call)|         1|  0.000438929|  0.000438929|  0.04%|# /usr/lib/python2.7/threading.py:436 __init__
    12|         1|  4.79221e-05|  4.79221e-05|  0.00%|t1.start()
(call)|         1|  0.000843048|  0.000843048|  0.08%|# /usr/lib/python2.7/threading.py:485 start
    13|         1|  6.48499e-05|  6.48499e-05|  0.01%|t2.start()
(call)|         1|   0.00115609|   0.00115609|  0.11%|# /usr/lib/python2.7/threading.py:485 start
    14|         1|  0.000205994|  0.000205994|  0.02%|(func(), func2())
(call)|         1|      1.00112|      1.00112| 99.54%|# demo/threads.py:4 func
(call)|         1|  3.09944e-05|  3.09944e-05|  0.00%|# demo/threads.py:7 func2
    15|         1|  7.62939e-05|  7.62939e-05|  0.01%|t1.join()
(call)|         1|  0.000423908|  0.000423908|  0.04%|# /usr/lib/python2.7/threading.py:653 join
    16|         1|  5.26905e-05|  5.26905e-05|  0.01%|t2.join()
(call)|         1|  0.000320196|  0.000320196|  0.03%|# /usr/lib/python2.7/threading.py:653 join

Observe que, como pprofile não depende de modificação de código, ele pode criar perfis de instruções de módulo de nível superior, permitindo definir o perfil do tempo de inicialização do programa (quanto tempo leva para importar módulos, inicializar globais, ...).

Ele pode gerar saída formatada em cachegrind, então você pode usar kcachegrind para navegar facilmente por grandes resultados.

Divulgação: Eu sou o autor pprofile.


1
+1 Obrigado por sua contribuição. Parece bem feito. Eu tenho uma perspectiva um pouco diferente - medir o tempo inclusivo gasto por declarações e funções é um objetivo. Descobrir o que pode ser feito para tornar o código mais rápido é um objetivo diferente. A diferença se torna dolorosamente óbvia à medida que o código fica grande - como 10 ^ 6 linhas de código. O código pode estar desperdiçando grande porcentagem de tempo. A forma como eu acho é pegando um pequeno número de amostras muito detalhadas e examinando-as com um olho humano - não resumindo. O problema é exposto pela fração de tempo que perde.
Mike Dunlavey

1
Você está certo, não mencionei o uso de pprofile quando se deseja criar um perfil de um subconjunto menor. Eu editei minha postagem para adicionar exemplos disso.
vpelletier

3
Isso é exatamente o que eu estava procurando: não intrusivo e extenso.
egpbos

1
Boa ferramenta, mas executa várias vezes mais devagar do que o código original.
Rominf de

4

Você pode obter ajuda do pacote line_profiler para este

1. Primeiro instale o pacote:

    pip install line_profiler

2. Use o comando mágico para carregar o pacote em seu ambiente python / notebook

    %load_ext line_profiler

3. Se você deseja traçar o perfil dos códigos para uma função,
faça o seguinte:

    %lprun -f demo_func demo_func(arg1, arg2)

você obterá uma boa saída formatada com todos os detalhes se seguir estas etapas :)

Line #      Hits      Time    Per Hit   % Time  Line Contents
 1                                           def demo_func(a,b):
 2         1        248.0    248.0     64.8      print(a+b)
 3         1         40.0     40.0     10.4      print(a)
 4         1         94.0     94.0     24.5      print(a*b)
 5         1          1.0      1.0      0.3      return a/b

4

Apenas para melhorar a resposta de @Joe Kington acima mencionada .

Para Python 3.x , use line_profiler :


Instalação:

pip install line_profiler

Uso:

Suponha que você tenha o programa main.pye, dentro dele, funções fun_a()e fun_b()que deseja traçar um perfil com relação ao tempo; você precisará usar o decorador @profileantes das definições de função. Por exemplo,

@profile
def fun_a():
    #do something

@profile
def fun_b():
    #do something more

if __name__ == '__main__':
    fun_a()
    fun_b()

O perfil do programa pode ser traçado executando o comando shell:

$ kernprof -l -v main.py

Os argumentos podem ser obtidos usando $ kernprof -h

Usage: kernprof [-s setupfile] [-o output_file_path] scriptfile [arg] ...

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -l, --line-by-line    Use the line-by-line profiler from the line_profiler
                        module instead of Profile. Implies --builtin.
  -b, --builtin         Put 'profile' in the builtins. Use 'profile.enable()'
                        and 'profile.disable()' in your code to turn it on and
                        off, or '@profile' to decorate a single function, or
                        'with profile:' to profile a single section of code.
  -o OUTFILE, --outfile=OUTFILE
                        Save stats to <outfile>
  -s SETUP, --setup=SETUP
                        Code to execute before the code to profile
  -v, --view            View the results of the profile in addition to saving
                        it.

Os resultados serão impressos no console como:

Total time: 17.6699 s
File: main.py
Function: fun_a at line 5

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    5                                           @profile
    6                                           def fun_a():
...

EDIT: Os resultados dos profilers podem ser analisados ​​usando o pacote TAMPPA . Usando-o, podemos obter gráficos desejados linha por linha como enredo


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.