¿Cómo mido el rendimiento del código elisp?

26

¿Cómo mido el rendimiento de mi código elisp? ¿Qué herramientas / paquetes externos están disponibles para mí para medir el tiempo necesario?

Además del tiempo total, ¿puedo ver un perfil que muestre el tiempo empleado por función? ¿Puedo perfilar el uso de memoria también?

Wilfred Hughes
fuente
1
La pregunta es demasiado amplia. ¿Qué tipo de desempeño? ¿Dónde? ¿Cuando? El " rendimiento de Emacs " puede significar cualquier cosa y todo.
Dibujó el
@Drew Muchos otros lenguajes de programación tienen un conjunto de puntos de referencia (por ejemplo, Python: speed.pypy.org , JS: Sunspider, etc.), y esperaba que hubiera un equivalente para el intérprete elisp.
Wilfred Hughes
El benchmarking como el proporcionado por la función benchmarky el generador de perfiles no mide el rendimiento de Emacs . Mide el rendimiento evaluando expresiones particulares. Es útil para comparar actuaciones dentro de Emacs. Para medir el rendimiento de Emacs en sí mismo, deberá compararlo con el rendimiento de algo distinto de Emacs. Y ahí es donde entra en juego la amplitud de Emacs. Podría medir Emacs vs XYZ para esto o aquello, pero para medir el rendimiento de Emacs en su conjunto necesitaría innumerables comparaciones de este tipo.
Dibujó el
Quizás quisiste decir " ¿Cómo mido el rendimiento en Emacs "?
Dibujó el
2
OK, abrí emacs.stackexchange.com/q/655/304 para hacer referencia a Emacs y reescribí esta pregunta sobre programas de evaluación de perfil / elisp.
Wilfred Hughes

Respuestas:

31

Punto de referencia

La opción más sencilla es el benchmarkpaquete integrado . Su uso es notablemente simple:

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

Está autocargado, por lo que ni siquiera necesita requerirlo.

Perfilado

El punto de referencia es bueno en las pruebas generales, pero si tiene problemas de rendimiento, no le indica qué funciones están causando el problema. Para eso, tiene el perfilador (también incorporado) .

  1. Comenzar con M-x profiler-start.
  2. Realiza algunas operaciones que requieren mucho tiempo.
  3. Obtenga el informe con M-x profiler-report.

Debería ser llevado a un búfer con un árbol de llamadas de función navegable.
Captura de pantalla de Profiler

Malabarba
fuente
benchmarkLa función no parece funcionar: cuando lo hago dentro de un .carchivo abierto (benchmark 100 (c-font-lock-fontify-region 0 17355)), sigo obteniendo void-function jit-lock-bounds.
Hola Ángel,
1
FTR: como alternativa a benchmarkhay funciones benchmark-runy benchmark-run-compiled. Para mí, la principal diferencia era que ambas funciones realmente funcionan (vea el comentario anterior) : Ь
Hola, Ángel,
14

Además de la respuesta de @ Malabara, tiendo a usar una with-timermacro personalizada para instrumentar permanentemente varias partes de mi código (por ejemplo, mi init.elarchivo).

La diferencia es que, si bien benchmarkpermite estudiar el rendimiento de un bit de código específico que usted instrumenta, with-timersiempre le brinda el tiempo que pasa en cada parte instrumentada del código (sin demasiada sobrecarga para partes suficientemente grandes), lo que le brinda la entrada para saber qué parte debe investigarse más a fondo.

(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))))))

Ejemplo de uso:

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

produciendo el siguiente resultado en el *Messages*búfer:

Doing things... done (0.047s)

Debo mencionar que esto está fuertemente inspirado por la use-package-with-elapsed-timermacro de Jon Wiegley en su excelente use-packageextensión.

ffevotte
fuente
Si está midiendo init.el, probablemente le interesará el generador de perfiles de inicio de emacs .
Wilfred Hughes
Las macros son asombrosas. Esto merece más votos.
Malabarba
2
Emacs registra el tiempo de inicio total. Puedes mostrarlo con el comando emacs-init-time.
Joe
1
@WilfredHughes sí, lo uso esupy me gusta. Pero una vez más, el interés de algo with-timerasí no es tanto perfilar algo minuciosamente. El verdadero interés es que siempre tenga información de perfil. Cada vez que inicio emacs, tengo una línea de líneas en mi *Messages*búfer que me dice qué parte tomó cuánto tiempo. Si detecto algo anormal, puedo usar cualquiera de las herramientas más adecuadas para perfilar y optimizar las cosas.
ffevotte
@JoeS Sí, emacs-init-timeproduce información interesante. Sin embargo, solo proporciona un tiempo transcurrido inclusivo, sin la posibilidad de desglosar partes individuales de la inicialización.
ffevotte
3

Además de la respuesta de @ Malabarba, tenga en cuenta que puede medir el tiempo de ejecución compilado de su código con benchmark-run-compiled. Esa métrica a menudo es mucho más relevante que el tiempo de ejecución interpretado que M-x benchmarkle brinda:

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)

Los tres números son el tiempo total transcurrido, el número de ejecuciones de GC y el tiempo pasado en GC.

Clemente
fuente
1

La evaluación comparativa no se trata solo de obtener los números, sino también de tomar decisiones basadas en el análisis de resultados.

Hay un paquete benchstat.el en MELPA que puede usar para obtener funciones que proporciona el programa benchstat .

Implementa una evaluación comparativa basada en la comparación en la que examina Xlas propiedades de rendimiento Y.

Las funciones de Benchstat se pueden ver como un benchmark-run-compiledcontenedor que no solo recopila la información, sino que la devuelve en un formato de interpretación fácil de leer. Incluye:

  • Tiempo transcurrido delta entre XyY
  • Tiempo promedio
  • Cantidad de asignaciones

Ejemplo de uso muy simple:

(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)

El benchstat-comparerenderizará resultados en un buffer temporal:

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)

Sin benchstatembargo, necesitará un programa binario. Si utilizó el lenguaje de programación Go, lo más probable es que ya tenga uno en su sistema. De lo contrario, existe la opción de compilarlo desde las fuentes.

El binario precompilado para linux / amd64 se puede encontrar en la página de lanzamiento de github .

Iskander Sharipov
fuente