Como faço para medir o desempenho do código elisp?

26

Como faço para medir o desempenho do meu código elisp? Quais ferramentas / pacotes externos estão disponíveis para eu medir o tempo gasto?

Além do tempo total, posso ver um perfil que mostra o tempo gasto por função? Também posso analisar o uso de memória?

Wilfred Hughes
fonte
11
A questão é muito ampla. Que tipo de performance? Onde? Quando? " Desempenho do Emacs " pode significar tudo e qualquer coisa.
Tirou
@Drew Muitas outras linguagens de programação têm um conjunto de parâmetros de referência (por exemplo, Python: speed.pypy.org , JS: Sunspider etc), e eu esperava que houvesse um equivalente para o intérprete elisp.
Wilfred Hughes
Benchmarking como o fornecido pela função benchmarke pelo criador de perfil não mede o desempenho do Emacs . Ele mede o desempenho avaliando expressões específicas. É útil na comparação de performances no Emacs. Para medir o desempenho do Emacs, você precisará compará-lo ao desempenho de algo diferente do Emacs. E é aí que a amplitude do Emacs entra em jogo. Você poderia medir o Emacs x XYZ para isso ou aquilo, mas para medir o desempenho do Emacs como um todo, você precisaria de muitas comparações.
Tirou
Talvez você quis dizer " Como faço para medir o desempenho no Emacs "?
Drew
2
OK, abri o emacs.stackexchange.com/q/655/304 para falar sobre o benchmarking do Emacs e reformulei essa pergunta sobre os programas elisp de benchmarking / criação de perfil.
Wilfred Hughes

Respostas:

31

Referência

As opções mais diretas são o benchmarkpacote embutido . Seu uso é notavelmente simples:

(benchmark 100 (form (to be evaluated)))

É carregado automaticamente, assim você nem precisa exigir.

Criação de perfil

O benchmark é bom em testes gerais, mas se você estiver com problemas de desempenho, não informará quais funções estão causando o problema. Para isso, você tem o criador de perfil (também interno ) .

  1. Comece com M-x profiler-start.
  2. Faça algumas operações demoradas.
  3. Obtenha o relatório com M-x profiler-report.

Você deve ser levado para um buffer com uma árvore navegável de chamadas de função.
Captura de tela do Profiler

Malabarba
fonte
benchmarkA função parece não funcionar: quando eu faço dentro de um .carquivo aberto (benchmark 100 (c-font-lock-fontify-region 0 17355)), continuo recebendo void-function jit-lock-bounds.
Hi-Angel
11
FTR: como alternativa a benchmarkexistem funções benchmark-rune benchmark-run-compiled. Para mim, a principal diferença era que as duas funções realmente funcionam (veja o comentário anterior) :
Hi
14

Além da resposta de @ Malabara, costumo usar uma with-timermacro personalizada para instrumentar permanentemente várias partes do meu código (por exemplo, meu init.elarquivo).

A diferença é que, embora benchmarkpermita estudar o desempenho de um bit específico de código que você instrumenta, with-timersempre fornece o tempo gasto em cada parte instrumentada do código (sem muita sobrecarga para partes suficientemente grandes), o que fornece a entrada para você saber qual parte deve ser investigada mais detalhadamente.

(defmacro with-timer (title &rest forms)
  "Run the given FORMS, counting the elapsed time.
A message including the given TITLE and the corresponding elapsed
time is displayed."
  (declare (indent 1))
  (let ((nowvar (make-symbol "now"))
        (body   `(progn ,@forms)))
    `(let ((,nowvar (current-time)))
       (message "%s..." ,title)
       (prog1 ,body
         (let ((elapsed
                (float-time (time-subtract (current-time) ,nowvar))))
           (message "%s... done (%.3fs)" ,title elapsed))))))

Exemplo de uso:

(with-timer "Doing things"
  (form (to (be evaluated))))

produzindo a seguinte saída no *Messages*buffer:

Doing things... done (0.047s)

Devo mencionar que isso é fortemente inspirado pela use-package-with-elapsed-timermacro de Jon Wiegley em sua excelente use-packageextensão.

ffevotte
fonte
Se você estiver medindo init.el, provavelmente estará interessado no perfil de inicialização do emacs .
Wilfred Hughes
Macros são incríveis. Isso merece mais votos.
Malabarba 01/10
2
O Emacs registra o tempo total de inicialização. Você pode mostrá-lo com o comando emacs-init-time.
Joe
11
@WilfredHughes sim, eu uso esupe gosto. Mas mais uma vez, o interesse de algo que with-timernão é tanto o perfil de algo completamente. O interesse real é que você sempre tenha informações de perfil. Sempre que inicio o emacs, tenho um monte de linhas no meu *Messages*buffer que me dizem qual parte levou quanto tempo. Se eu detectar algo anormal, posso usar qualquer uma das ferramentas mais adequadas para criar um perfil e otimizar as coisas.
Ffevotte 1/10/2014
@ JoeS Sim, emacs-init-timeproduz informações interessantes. No entanto, ele fornece apenas um tempo decorrido inclusivo, sem a possibilidade de quebrar partes individuais da inicialização.
Ffevotte 1/10
3

Além da resposta de @ Malabarba, observe que você pode medir o tempo de execução compilado do seu código com benchmark-run-compiled. Essa métrica geralmente é muito mais relevante do que o tempo de execução interpretado que M-x benchmarkfornece:

ELISP> (benchmark-run (cl-loop for i below (* 1000 1000) sum i))
(0.79330082 6 0.2081620540000002)

ELISP> (benchmark-run-compiled (cl-loop for i below (* 1000 1000) sum i))
(0.047896284 0 0.0)

Os três números são o tempo total decorrido, o número de execuções do GC e o tempo gasto no GC.

Clemente
fonte
1

O benchmarking não é apenas obter os números, mas também tomar decisões com base na análise de resultados.

um pacote benchstat.el no MELPA que você pode usar para obter recursos que o programa benchstat fornece.

Ele implementa benchmarking baseado em comparação, no qual você examina Xas propriedades de desempenho Y.

As funções do Benchstat podem ser vistas como um benchmark-run-compiledinvólucro que não apenas coleta as informações, mas as devolve em um formato de interpretação fácil de ler. Inclui:

  • Delta do tempo decorrido entre XeY
  • Tempo médio
  • Montante das alocações

Exemplo de uso muito simples:

(require 'benchstat)

;; Decide how much repetitions is needed.
;; This is the same as `benchmark-run-compiled` REPETITIONS argument.
(defconst repetitions 1000000)

;; Collect old code profile.
(benchstat-run :old repetitions (list 1 2))
;; Collect new code profile.
(benchstat-run :new repetitions (cons 1 2))

;; Display the results.
;; Can be run interactively by `M-x benchstat-compare'.
(benchstat-compare)

O benchstat-comparerenderizará resultados em um buffer temporário:

name   old time/op    new time/op    delta
Emacs    44.2ms ± 6%    25.0ms ±15%  -43.38%  (p=0.000 n=10+10)

name   old allocs/op  new allocs/op  delta
Emacs      23.0 ± 0%      11.4 ± 5%  -50.43%  (p=0.000 n=10+10)

Você precisará do benchstatprograma binário. Se você usou a linguagem de programação Go, provavelmente já possui uma no seu sistema. Caso contrário, há uma opção de compilá-lo a partir das fontes.

O binário pré-compilado para linux / amd64 pode ser encontrado na página de lançamento do github .

Iskander Sharipov
fonte